我坚信以下设计理念:
1>应尽可能在存储数据的地方实施服务.
2> Getter和Setter是邪恶的,应该小心使用.
我宁愿不在这里争论两个论点,并假设他们有自己的优势.
这是我目前面临的挑战.我有两个类(即AComputer和A),其中AComputer为A提供一些服务,A保存所有基本数据成员.
事实:由于系统设计,我不允许在AComputer内部组合A.我知道,它已经打破了我的观点1>计算应该与数据保持一致.
当从中传递数据A时AComputer,我们必须传递10个(大约)单个参数,因此最好设计一个结构来执行此操作,否则构造函数列表将变得疯狂.存储的大多数数据AComputer都是存储的数据的直接副本A.我们选择将这些数据存储在内部,AComputer因为其他函数AComputer也需要这些变量.
这是一个问题(我要求考虑API维护和修改的最佳实践):
1>我们应该在哪里定义通道结构PassData?
2>我们应该为struct提供getter/setter PassData吗?
我提供了一个示例代码,以便详细说明我的问题.我最好能找到一个真正的工作开源API来解决同一个问题,这样我就可以从中学习.
如果你看一下PassData m_data;课堂上的私人定义AComputer,我是故意这样做的.换句话说,如果我们改变底层实现AComputer,我们可以PassData m_data;用单个变量或其他东西替换但不要破坏接口PassData.所以在这个设计中,我没有为结构提供getter/setter PassData.
谢谢
class AComputer
{
public:
struct PassData
{ // int type just used as an illustration. Real data has different types,
// such as double, data, string, enum, etc.
// Note: they are not exact copies of variables from A but derived from them
int m_v1;
// from m_v1 to m_v10
//...
int m_v10;
};
// it is better to store the passed-in data since other functions also need it.
AComputer(const PassData& pd) : m_data(pd) {}
int GetCombinedValue() const
{ /* This function returns a value based the passed-in struct of pd */ }
private:
PassData m_data;
};
class A
{
private:
int m_i1;
// from m_i1 to m_i10
// ...
int m_i10;
// from m_i11 to m_i20
// ...
int m_i20;
boost::shared_ptr<AComputer> m_pAComputer;
public:
A()
{
AComputer::PassData aData;
// populate aData ...
m_pAComputer = boost::shared_ptr<AComputer>(new AComputer(aData));
}
int GetCombinedValue() const
{
return m_pAComputer->GetCombinedValue();
}
};
Run Code Online (Sandbox Code Playgroud)
Ale*_*zzi 11
我认为在开始之前最好澄清几点,你说:
如果你看私人PassData m_data; 在AC计算机中定义,我是故意这样做的.换句话说,如果我们改变了AComputer的底层实现,我们可以替换PassData m_data; 使用单个变量或其他东西,但不要破坏PassData的接口.
事实并非如此,PassData是您界面的一部分!您不能在不破坏客户端代码的情况下替换PassData,因为您需要在AComputer的构造函数中使用PassData.PassData不是实现细节,但它是纯接口.
第二点需要澄清:
2> Getter和Setter是邪恶的,应该小心使用.
正确!但是你应该知道POD(Plain-Old-Data结构)甚至是最差的.使用POD而不是使用getter和setter类的唯一优点是可以省去编写函数的麻烦.但真正的问题仍然是开放的,你班级的界面太麻烦,维护起来非常困难.
设计始终是不同要求之间的权衡:
虚假的灵活感
您的图书馆已经发布,很多代码都在使用您的课程.在这种情况下,PassData的变化将是戏剧性的.如果您可以在运行时支付一小笔费用,那么您可以灵活地使用界面.例如,AComputer的构造函数将是:
AComputer(const std::map<std::string,boost::any>& PassData);
Run Code Online (Sandbox Code Playgroud)
在这里看一下boost :: any .您还可以为地图提供工厂,以帮助用户轻松创建地图.
临
缺点
总的来说这个解决方案并不好,最后它只是原版的一个奇特版本.
战略模式
struct CalculateCombinedValueInterface
{
int GetCombinedValue()=0;
virtual ~CalculateCombinedValueInterface(){}
};
class CalculateCombinedValueFirst : CalculateCombinedValueInterface
{
public:
CalculateCombinedValueFirst(int first):first_(first){}
int GetCombinedValue(); //your implementation here
private:
//I used one field but you get the idea
int first_;
};
Run Code Online (Sandbox Code Playgroud)
客户端代码将是:
CalculateCombinedValueFirst* values = new CalculateCombinedValueFirst(42);
boost::shared_ptr<CalculateCombinedValueInterface> data(values);
Run Code Online (Sandbox Code Playgroud)
现在,如果要修改代码,则不应触及已部署的接口.面向对象的解决方案是提供一个从抽象类继承的新类.
class CalculateCombinedValueSecond : CalculateCombinedValueInterface
{
public:
CalculateCombinedValueFirst(int first,double second)
:first_(first),second_(second){}
int GetCombinedValue(); //your implementation here
private:
int first_;
double second_;
};
Run Code Online (Sandbox Code Playgroud)
客户端将决定升级到新类还是保留现有版本.
临
缺点
参数数量
如果在一个函数中输入了一组十个参数,则这些值很可能与逻辑相关.您可以在课程中收集其中一些值.这些类可以组合在另一个类中,它将作为函数的输入.你在一个类中有10个(或更多!)数据成员的事实应该响铃.
该单一职责原则说:
一个班级改变的理由绝不应该是一个原因.
这个原则的必然结果是:你的班级要小.如果你的班级有20个数据成员,你很可能会发现很多改变它的理由.
结论
在向客户端提供接口(任何类型的接口)之后,您无法对其进行更改(一个很好的示例是C++中编译器需要实现多年的所有弃用功能).请注意您提供的界面甚至是隐式界面.在您的示例中,PassData不是实现细节,但它是类接口的一部分.
参数数量是需要检查设计的信号.改变一个大班很难.你的类应该很小,只能通过接口(C++ slang中的抽象类)依赖于其他类.
如果你的班级是:
1)小而且只有一个原因需要改变
2)派生自抽象类
3)其他类使用指向抽象类的指针来引用它
您的代码可以轻松更改(但必须保留已提供的界面).
如果您不满足所有这些要求,您将遇到麻烦.
注意:要求2)和3)可以改变,如果不提供动态多态,设计使用静态多态.
您可能会考虑重构以使用模式对象——该对象的唯一目的是包含方法调用的参数。有关更多详细信息:http://sourcemaking.com/refactoring/introduce-parameter-object
| 归档时间: |
|
| 查看次数: |
1315 次 |
| 最近记录: |