要集中精力设计定义了API 80%功能的20%的类.
使用继承
避免深度继承树。很深的继承层次结构增加了设计的复杂性,这总是会使设计难以理解,软件也更容易走向失败。给继承深度加一个绝对的数量限制很明显是主观的,但是任何多于两层或三层的继承层次结构已经变得太复杂了(McConnel1,2004)
里氏替换原则
相对于“is-a”,LSP是一个更具限制性的定义。
让我们用一个经典的例了一椭圆形状类型对此加以说明:
class Ellipse {
public :
public Ellipse();
Ellipse(float major, float minor);
void SetMajorRadius(float major):
void SetMinorRadius(float minor);
float GetMajorRadius() const:
float GetMinorRadius() const;
private:
float mMajor
float mMinor:
};
然后,你可以决定添加一个圆形类。从数学的角度看,圆是椭圆的一种更具体形态,它的两个轴是相等的。因此,将Circle类声明为E]1ipse类的子类。比如,
class Circle : public Ellipse
public:
Circle();
explicit Circle(float r):
void SetRadius(float r);
float GetRadius( ) const;
};
SetRadius()的实现可以将下层圆的长轴和短轴设置为同一个值来保证圆的属性。
void Circle::SetRadius(float r)
SetMajorRadius(r)
SetMinorRadius(r);
}
float Circle::GetRadius() const
{
return GetMajorRadius():
}
这造成了很多问题。最明显的问题是Circle类继承并暴露了Ellipse类的SetMajorRadius()和SetMinorRadius()方法。用户可以利用这些方法修改一个半径却不修改另一个,这就破坏了圆的自身一致性。可以通过重写这两个方法来处理这一问题,即在每个方法中同时设置长轴和短轴。但这又造成 创建了非正交的API: 改变一个属性对改变另一个届性有副作用。最后,你破坏了Liskov替换原则,因为你无法在不破坏行为的情况下用Circle来替换E1ipse。
所以,不应该使用公有继承将圆形建模为一种椭圆形,那应该如何表示呢?基于E11ipse类的功能,有两种主要的方式可以正确地构建circle类:私有继承和组合。
class Circle {
public:
Circle();
explicit Circle(float r);
void SetRadius(float r);
float GetRadius( ) const;
private:
E1lipse mEllipse;
};
SetRadius()和GetRadius()两个方法的定义看起来可能是这样的:
void Circle::SetRadius(float r)
{
mEllipse.SetMajorRadius(r);
mEllipse.SetMinorRadius(r);
}
float Circle::GetRadius() const
{
return mEllipse.GetMajorRadius();
}
在这个例子中,Ellipse的接口没有暴露在Circle的接口中。通过创建一个私有的Ellipse实例,Circle仍然是构建在Elipse的功能之上。
里氏替换原则指出,在不修改任何行为的情况下用派生类替换基类,这应该总是可行的。
组合优先于继承。
开放-封闭原则
开放-封闭原则( Open/Closed Principle,OCP)是由BertrandMeyer引l入的,该原则指出,类的目标应该是为扩展而开放,为修改而关闭的(Meyer,1997 )。创建可以长期使用的稳定接口。
OCP背后的重要理念是,一旦一个类创建完成并向用户发布,唯一可以修改的就只有编程错误了,新特性的添加或功能的修改应该通过创建新类的方式实现。
OCP可以被认为是一种启发式的指导原则,而非必须遵守的原则。
API应该为不兼容的接口变化而关闭,为功能扩展而开放。
参考:
- C++ API设计