研究員:啓用多個後端經由策略(戰略)模式
我的工作的,其作用於類,如「身體」,「航天器」,「星球」等各建設算法的集合實例可以通過不同的後端來完成。例如,我可以使用多種庫(如NASA的SPICE系統)來計算行星位置,並且我還可以使用多種數據源和庫來「計算」一個天體的半徑。我的算法集合應該忽略數據源:例如,如果我想計算日食的時間,我只關心物體的相對位置以及它們的半徑(無論我在哪裏得到這些數據數字來自)。
在下面的代碼中,我使用策略類來對兩個不同的後端進行參數化(簡化,因爲這是一個示例)。我有興趣提出以下問題:
- 爲此目的使用策略模式是否合理?
- 我怎樣可以刪除包含在類的構造函數的實現細節,並將其移動到平普爾構造函數(參數BodyImpl?)
的代碼是一個有點冗長,但我想我的理由「通過交談」 。
謝謝。
予編譯成功下面使用克++ 4.7.2代碼,如下所示:
g++ backends.cpp -std=c++11 -Wall -O2
(通知它使用幾個C++ 11點的構建體,如auto
)。
/**
Is it reasonable to parameterize different data back-ends using the
Policy Pattern?
The goal is to provide a unified interface to different classes
(e.g., `Body`, `Star`, `Spacecraft`). However, the construction of
specific instances requires data which can originate from different
sources.
For example, a "Body" has a radius and a gravitational parameter
(called "gm"). But these values can come from different sources
(different libraries which provide this kind of information).
Say that library 1 (called "Spice") is capable of providing the
radius given the body name:
double the_radius = compute_radius_with_spice("Mercury");
On the other hand, you could be using another library, which
computes the radius with a completely different interface, and with
completely different requirements:
double radii[3];
compute_the_radius_with_another_library("Mercury", radii)
double the_radius = (radii[0] + radii[1] + radii[2])/3.0;
Of course, the values computed with either library are similar, but
different enough to make a difference. What matters is CONSISTENCY
(stick to one back-end).
*/
#include<iostream>
#include<string>
#include<vector>
#include<memory>
/* Say that this is the uniform interface that I want to provide.*/
template<typename DataPolicy>
class Body:
private DataPolicy{
public:
Body(const std::string& name);
Body(const Body& body);
~Body();
std::string name() const;
double radius() const;
double gm() const;
private:
class BodyImpl * pimpl_;;
// std::unique_ptr<BodyImpl> pimpl_;
};
/* I use the pimpl_ idiom to hide the implementation */
struct BodyImpl{
std::string m_name;
double m_radius;
double m_gm;
BodyImpl(const std::string& name):
m_name(name){
}
};
/* The constructor has to build the pimpl step by step using the data
policy as a data source. */
template<typename DataPolicy>
Body<DataPolicy>::Body(const std::string& name):
pimpl_(new BodyImpl(name)){
pimpl_->m_radius = DataPolicy::get_radius(name);
pimpl_->m_gm = DataPolicy::get_gm(name);
}
template<typename DataPolicy>
Body<DataPolicy>::Body(const Body& body):
pimpl_(new BodyImpl(body.name())){
pimpl_->m_radius = body.radius();
pimpl_->m_gm = body.gm();
}
template<typename DataPolicy>
Body<DataPolicy>::~Body(){
delete pimpl_;
pimpl_ = 0;
}
/* The methods are simple forwarding calls to the implementation (in
reality it is not as simple as returning a primitive data type)*/
template<typename DataPolicy>
std::string Body<DataPolicy>::name() const{
return pimpl_->m_name;
}
template<typename DataPolicy>
double Body<DataPolicy>::radius() const{
return pimpl_->m_radius;
}
template<typename DataPolicy>
double Body<DataPolicy>::gm() const{
return pimpl_->m_gm;
}
/* Now I create a concrete data policy - in reality this would be more
extensive and complex, but the idea remains the same */
struct SPICEDataPolicy{
static double get_radius(const std::string& name){
std::cout<<"SPICEDataPolicy: calculating radius for "<<name<<std::endl;
return 0;
}
static double get_gm(const std::string& name){
std::cout<<"SPICEDataPolicy: calculating gm for "<<name<<std::endl;
return 0;
}
};
/* This is another data policy - it provides the same data but it may
call a completely different underlying library, and calculate the
values using completely different logic */
struct OtherDataPolicy{
static double get_radius(const std::string& name){
std::cout<<"OtherDataPolicy: calculating radius for "<<name<<std::endl;
return 0;
}
static double get_gm(const std::string& name){
std::cout<<"OtherDataPolicy: calculating gm for "<<name<<std::endl;
return 0;
}
};
/* My algorithms can now use the objects via the unified interface */
template<typename T>
void individual_complex_calculation(const Body<T>& body){
// Regardless of the body's data policy, I know I can call a uniform interface.
std::cout<<"I am making a complex calculation involving "<<body.name()<<"."<<std::endl
<<"[This is my radius: "<<body.radius()<<", "
<<"and this is my gm: "<<body.gm()<<"]"<<std::endl;
}
template<typename T>
void complex_calculation(const std::vector<Body<T> > bodies){
for(auto it=bodies.begin(), finished=bodies.end(); it!=finished; it++)
individual_complex_calculation(*it);
}
int main(){
/* Now I can create a vector of bodies which are consistent with one
another */
std::cout<<"========== Using 'SPICEDataPolicy =========='"<<std::endl;
std::vector<Body<SPICEDataPolicy> > bodies;
bodies.push_back(Body<SPICEDataPolicy>("Mercury"));
bodies.push_back(Body<SPICEDataPolicy>("Venus"));
bodies.push_back(Body<SPICEDataPolicy>("Earth"));
bodies.push_back(Body<SPICEDataPolicy>("Mars"));
complex_calculation(bodies);
/* And even create other set of bodies consistent with one another,
but inconsistent with the previous ones.*/
std::cout<<"========== Using 'OtherDataPolicy' =========="<<std::endl;
std::vector<Body<OtherDataPolicy> > other_bodies;
other_bodies.push_back(Body<OtherDataPolicy>("Mercury"));
other_bodies.push_back(Body<OtherDataPolicy>("Venus"));
other_bodies.push_back(Body<OtherDataPolicy>("Earth"));
other_bodies.push_back(Body<OtherDataPolicy>("Mars"));
complex_calculation(other_bodies);
return 0;
}
的./a.out
輸出:
========== 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);
}
謝謝,Rob - 我已經考慮過抽象工廠模式,但它有點不像擴展那麼容易(我需要提供方法來構建我寫的「樣本」中的每種對象)。特質方法更接近(見編輯答案)。 – Escualo
是啊,看起來不錯,@阿列塔。順便說一句,這裏有一些有趣的附加信息:http://www.cantrip.org/traits.html但看起來也許你看到它。我不完全清楚你對抽象工廠的看法。你可以在類的抽象版本中使用一些私有方法,然後覆蓋只是以不同的方式來操作它們。坦率地說,如果沒有真正的功能差異,那麼你需要的是一個適配器。 – Rob