C++ 通过策略模式启用多个后端

C++ 通过策略模式启用多个后端,c++,design-patterns,architecture,C++,Design Patterns,Architecture,研究员: 我正在研究一系列算法,这些算法作用于“Body”、“Sparter”、“Planet”等类。每个实例的构造都可以通过不同的后端完成。例如,我可以使用多种库(如NASA的SPICE系统)计算行星位置,也可以使用多种数据源和库“计算”天体半径 我的算法集合应该不受数据源的影响:例如,如果我想计算日食的时间,我只关心物体的相对位置和半径(不管这些数字是从哪里得到的) 在下面所附的代码中,我使用策略类来参数化两个不同的后端(由于这是一个示例,所以进行了简化)。我有兴趣提出以下问题: 为此目的

研究员:

我正在研究一系列算法,这些算法作用于“Body”、“Sparter”、“Planet”等类。每个实例的构造都可以通过不同的后端完成。例如,我可以使用多种库(如NASA的SPICE系统)计算行星位置,也可以使用多种数据源和库“计算”天体半径

我的算法集合应该不受数据源的影响:例如,如果我想计算日食的时间,我只关心物体的相对位置和半径(不管这些数字是从哪里得到的)

在下面所附的代码中,我使用策略类来参数化两个不同的后端(由于这是一个示例,所以进行了简化)。我有兴趣提出以下问题:

  • 为此目的使用政策模式是否合理
  • 如何删除类构造函数中包含的实现细节并将它们移动到Pimpl构造函数(ParameterizeBodyImpl?)
代码有点冗长,但我想“谈谈”我的基本原理

多谢各位

我使用g++4.7.2成功编译了以下代码,如下所示:

g++ backends.cpp -std=c++11 -Wall -O2
(注意,它使用了一些c++11结构,如
auto

编辑: 我尝试了另一个基于“特征”和“策略”的实现。感觉干净多了,但我还是很好奇你对它的看法

下面的代码使用与上面相同的命令行参数编译

/**
   Multiple back-ends implemented as a mix of trait classes and policies.

   This seems to be a better implementation because there is a clear
   path to extend the different back-ends, and the class front-end is
   completely independent from its back-end.

*/

#include<iostream>
#include<string>
#include<vector>
#include<memory>

// forward declaration of the trait "data_traits"
template<typename T>
struct data_traits{
};

// each class would be defined as follows (with forward declaration of
// its implementation class)
template<typename T>
struct BodyImpl;

template<typename T>
class Body{
public:
  Body(const std::string& name);
  std::string name() const;
  double radius() const;
  double gm() const;
private:
  std::unique_ptr<BodyImpl<T> > pimpl_;
};

// each class would be implemented in a cpp file with the following
// structure (notice full independence from any back-end)
template<typename T>
struct BodyImpl{
  std::string m_name;
  double m_radius;
  double m_gm;
  BodyImpl(const std::string& name):
    m_name(name){
    m_radius = data_traits<T>::get_radius(name);
    m_gm = data_traits<T>::get_gm(name);
  }    
};

/* public interface simply forwards to pimpl */
template<typename T>
Body<T>::Body(const std::string& name):
  pimpl_(new BodyImpl<T>(name)){
}

template<typename T>
std::string Body<T>::name() const{
  return pimpl_->m_name;
}

template<typename T>
double Body<T>::radius() const{
  return pimpl_->m_radius;
}

template<typename T>
double Body<T>::gm() const{
  return pimpl_->m_gm;
}


/* the user or library writer can then write specific back-ends
   according to the following interfaces */
struct SPICEBackEnd;
template<> struct data_traits<SPICEBackEnd>{
  static double get_radius(const std::string& name){
    std::cout<<"[SPICE] get radius for "<<name<<std::endl;
    return 0;
  }
  static double get_gm(const std::string& name){
    std::cout<<"[SPICE] get gm for "<<name<<std::endl;
    return 0;
  }
};

/*another back-end*/
struct OtherBackEnd;
template<> struct data_traits<OtherBackEnd>{
  static double get_radius(const std::string& name){
    std::cout<<"[OTHER] get radius for "<<name<<std::endl;
    return 0;
  }
  static double get_gm(const std::string& name){
    std::cout<<"[OTHER] get gm for "<<name<<std::endl;
    return 0;
  }
};

/* The algorithms can be obvlivious to the back-end used */
template<typename T>
void complex_calculation(const std::vector<Body<T> >& bodies){
  for(auto it=bodies.begin(), finished=bodies.end(); it!=finished; it++){
    std::cout<<"Body "<<it->name()<<" (r="<<it->radius()<<", mu="<<it->gm()<<")"<<std::endl;
  }
}


int main(){
  std::vector<Body<SPICEBackEnd> > spice_bodies;
  spice_bodies.push_back(Body<SPICEBackEnd>("Mercury"));
  spice_bodies.push_back(Body<SPICEBackEnd>("Venus"));
  spice_bodies.push_back(Body<SPICEBackEnd>("Earth"));
  spice_bodies.push_back(Body<SPICEBackEnd>("Mars"));

  complex_calculation(spice_bodies);

  std::vector<Body<OtherBackEnd> > other_bodies;
  other_bodies.push_back(Body<OtherBackEnd>("Mercury"));
  other_bodies.push_back(Body<OtherBackEnd>("Venus"));
  other_bodies.push_back(Body<OtherBackEnd>("Earth"));
  other_bodies.push_back(Body<OtherBackEnd>("Mars"));

  complex_calculation(other_bodies);
}
/**
作为trait类和策略的混合实现的多个后端。
这似乎是一个更好的实现,因为有一个明确的
扩展不同后端的路径,类前端是
完全独立于后端。
*/
#包括
#包括
#包括
#包括
//性状“数据性状”的正向声明
模板
结构数据特征{
};
//每个类的定义如下(带有
//它的实现类)
模板
结构体impl;
模板
阶级团体{
公众:
正文(const std::字符串和名称);
std::string name()常量;
双半径()常数;
双gm()常数;
私人:
标准::唯一性;
};
//每个类都将在一个cpp文件中实现,其中包含以下内容
//结构(注意完全独立于任何后端)
模板
结构体impl{
std::字符串m_名称;
双m_半径;
双m_gm;
BodyImpl(常量标准::字符串和名称):
m_姓名(姓名){
m_半径=数据_特征::获取_半径(名称);
m_gm=数据特征::获取(名称);
}    
};
/*公共接口只是转发给pimpl*/
模板
Body::Body(const std::string和name):
pimpl_(新BodyImpl(名称)){
}
模板
std::string Body::name()常量{
返回pimpl_->m_名称;
}
模板
双主体::半径()常量{
返回pimpl_->m_半径;
}
模板
双主体::gm()常量{
返回pimpl->m\U gm;
}
/*然后,用户或库编写器可以编写特定的后端
根据以下接口*/
后端结构;
模板结构数据{
静态双get_半径(常量标准::字符串和名称){
std::cout有趣的问题

有许多不同的方法值得考虑,也许最突出的是。为什么?因为你可以构造一系列符合基本接口集的对象,然后使用它们,而无需经常检查你应该做什么。另外,因为你强调了一致性

我所看到的战略问题是,它通常是一种封装做同一件事的不同方法的方法。例如,如果我们做工资单,每个人都有一个系统,同意应税工资是毛额扣除额,但该值的推导方式可能不同(坦率地说,在工资单中,“抽象工厂”可能也有道理,因为毫无疑问,您需要不止一个变量,而且一旦您起草了一个变量,其他所有变量必须来自同一个家族)


在设计方面,另一个有趣的因素是,您需要在一些非常不同的实体上计算一些通用的度量。这是Java接口的最大优势之一,和/或Objective-C或Scala等语言的特点(来自可笑的辉煌自我)我已经有相当长的一段时间没有写过很多C++了,但是我知道有一些方法可以完成一些特性,例如(James Coplien)。

谢谢,Rob,我已经考虑过抽象工厂模式,但是它看起来有些不容易扩展(我需要提供方法来构建我所写的“样本”中的每一种对象)。.特征方法更接近(见编辑答案).是的,看起来不错,@Arrieta.BTW,这里有一些有趣的附加信息:但看起来可能你看到了。我不完全清楚你对抽象工厂的看法。你可以在类的抽象版本中有一堆私有方法,然后重写只是以不同的方式操纵它们。坦白说,如果如果没有真正的功能差异,那么您需要的是一个适配器。
========== Using 'SPICEDataPolicy =========='
SPICEDataPolicy: calculating radius for Mercury
SPICEDataPolicy: calculating gm for Mercury
SPICEDataPolicy: calculating radius for Venus
SPICEDataPolicy: calculating gm for Venus
SPICEDataPolicy: calculating radius for Earth
SPICEDataPolicy: calculating gm for Earth
SPICEDataPolicy: calculating radius for Mars
SPICEDataPolicy: calculating gm for Mars
I am making a complex calculation involving Mercury.
[This is my radius: 0, and this is my gm: 0]
I am making a complex calculation involving Venus.
[This is my radius: 0, and this is my gm: 0]
I am making a complex calculation involving Earth.
[This is my radius: 0, and this is my gm: 0]
I am making a complex calculation involving Mars.
[This is my radius: 0, and this is my gm: 0]
========== Using 'OtherDataPolicy' ==========
OtherDataPolicy: calculating radius for Mercury
OtherDataPolicy: calculating gm for Mercury
OtherDataPolicy: calculating radius for Venus
OtherDataPolicy: calculating gm for Venus
OtherDataPolicy: calculating radius for Earth
OtherDataPolicy: calculating gm for Earth
OtherDataPolicy: calculating radius for Mars
OtherDataPolicy: calculating gm for Mars
I am making a complex calculation involving Mercury.
[This is my radius: 0, and this is my gm: 0]
I am making a complex calculation involving Venus.
[This is my radius: 0, and this is my gm: 0]
I am making a complex calculation involving Earth.
[This is my radius: 0, and this is my gm: 0]
I am making a complex calculation involving Mars.
[This is my radius: 0, and this is my gm: 0]
/**
   Multiple back-ends implemented as a mix of trait classes and policies.

   This seems to be a better implementation because there is a clear
   path to extend the different back-ends, and the class front-end is
   completely independent from its back-end.

*/

#include<iostream>
#include<string>
#include<vector>
#include<memory>

// forward declaration of the trait "data_traits"
template<typename T>
struct data_traits{
};

// each class would be defined as follows (with forward declaration of
// its implementation class)
template<typename T>
struct BodyImpl;

template<typename T>
class Body{
public:
  Body(const std::string& name);
  std::string name() const;
  double radius() const;
  double gm() const;
private:
  std::unique_ptr<BodyImpl<T> > pimpl_;
};

// each class would be implemented in a cpp file with the following
// structure (notice full independence from any back-end)
template<typename T>
struct BodyImpl{
  std::string m_name;
  double m_radius;
  double m_gm;
  BodyImpl(const std::string& name):
    m_name(name){
    m_radius = data_traits<T>::get_radius(name);
    m_gm = data_traits<T>::get_gm(name);
  }    
};

/* public interface simply forwards to pimpl */
template<typename T>
Body<T>::Body(const std::string& name):
  pimpl_(new BodyImpl<T>(name)){
}

template<typename T>
std::string Body<T>::name() const{
  return pimpl_->m_name;
}

template<typename T>
double Body<T>::radius() const{
  return pimpl_->m_radius;
}

template<typename T>
double Body<T>::gm() const{
  return pimpl_->m_gm;
}


/* the user or library writer can then write specific back-ends
   according to the following interfaces */
struct SPICEBackEnd;
template<> struct data_traits<SPICEBackEnd>{
  static double get_radius(const std::string& name){
    std::cout<<"[SPICE] get radius for "<<name<<std::endl;
    return 0;
  }
  static double get_gm(const std::string& name){
    std::cout<<"[SPICE] get gm for "<<name<<std::endl;
    return 0;
  }
};

/*another back-end*/
struct OtherBackEnd;
template<> struct data_traits<OtherBackEnd>{
  static double get_radius(const std::string& name){
    std::cout<<"[OTHER] get radius for "<<name<<std::endl;
    return 0;
  }
  static double get_gm(const std::string& name){
    std::cout<<"[OTHER] get gm for "<<name<<std::endl;
    return 0;
  }
};

/* The algorithms can be obvlivious to the back-end used */
template<typename T>
void complex_calculation(const std::vector<Body<T> >& bodies){
  for(auto it=bodies.begin(), finished=bodies.end(); it!=finished; it++){
    std::cout<<"Body "<<it->name()<<" (r="<<it->radius()<<", mu="<<it->gm()<<")"<<std::endl;
  }
}


int main(){
  std::vector<Body<SPICEBackEnd> > spice_bodies;
  spice_bodies.push_back(Body<SPICEBackEnd>("Mercury"));
  spice_bodies.push_back(Body<SPICEBackEnd>("Venus"));
  spice_bodies.push_back(Body<SPICEBackEnd>("Earth"));
  spice_bodies.push_back(Body<SPICEBackEnd>("Mars"));

  complex_calculation(spice_bodies);

  std::vector<Body<OtherBackEnd> > other_bodies;
  other_bodies.push_back(Body<OtherBackEnd>("Mercury"));
  other_bodies.push_back(Body<OtherBackEnd>("Venus"));
  other_bodies.push_back(Body<OtherBackEnd>("Earth"));
  other_bodies.push_back(Body<OtherBackEnd>("Mars"));

  complex_calculation(other_bodies);
}