# 本人学习时的笔记,有错必改,不喜勿喷 #
(接上一篇C++设计模式:设计原则0)
- 不应该强迫客户程序依赖它们不用的方法:即接口设计应该精简,不应该强迫客户程序实现它们不需要的接口方法。一个类应该只依赖于它需要的接口,而不应该依赖于它不需要的接口。
- 接口应该小而完备:即接口应该设计得尽可能小,只包含客户程序需要的方法,而且这些方法应该是相关的,彼此之间的关系应该紧密。这样可以提高接口的内聚性,降低客户程序的耦合度。
举例:比如一个动物园管理系统,主要有飞禽类和走兽类,这两种类别的动物肯定有不同的行为,因此我们应该分开定义飞禽基类IBird以及走兽基类IBeast,各自实现不同的具体方法,而不是设计一个包含所有动物行为的接口。
总结:接口隔离主要是为了避免一个基类太过于通用而导致接口混乱的情况,在开发中我们应该注意让每个类都有各自的特点。
6. 优先使用对象组合,而不是类继承
- 类继承通常为“白箱复用”,对象组合通常为“黑箱复用”。
- 类继承的“白箱复用”:当一个类继承另一个类时,它会继承其父类的接口和实现细节,这就意味着子类可以直接访问父类的内部数据和方法。这样的复用被称为“白箱复用”,因为子类可以直接看到父类的实现细节,从而可能导致对父类实现的依赖,增加了耦合度。
- 对象组合的“黑箱复用”:对象组合是通过将一个类的对象嵌入到另一个类中来实现复用。被组合的对象通常被视为“黑箱”,因为外部类不需要了解被组合对象的内部实现细节,只需通过定义的接口与其交互。这种方式降低了类之间的耦合度,提高了系统的灵活性和可维护性。
- 继承在某种程度上破坏了封装性,子类父类耦合度高。而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。
如上例中,很明显Duck类只是有fly这个行为,因为它应该是与FlyBehavior类组合而不是继承。
总结:如果我们想让类b能看到类a的所有行为以及数据,则应用继承;如果类b只是用了类a的某个行为,则应用组合。
7. 封装变化点
使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。
封装变化点包括以下几个方面:
- 识别变化点: 首先,需要识别系统中可能发生变化的地方,这些地方就是变化点。例如,可能会发生变化的地方包括业务逻辑、数据持久化、用户界面等。
- 封装变化: 一旦识别了变化点,就可以通过封装来创建分界层,将可能发生变化的部分与稳定的部分隔离开来。这样,当变化发生时,只需修改分界层的一侧,而不会对其他部分产生影响。
- 保持稳定: 封装变化点后,需要确保分界层的另一侧保持稳定。这意味着分界层的一侧应该尽量减少对另一侧的依赖,从而使得变化局限在分界层的一侧。
举例:假设有一个电商系统,其中包括订单处理模块和支付模块。订单处理模块负责处理订单,而支付模块负责处理支付操作。为了实现封装变化点,可以创建一个支付接口作为分界层,订单处理模块通过该接口与支付模块进行交互。这样,当支付方式发生变化时,只需修改支付模块的实现,而不会影响订单处理模块。
在这个例子中,Payment接口作为分界层,将订单处理模块和具体的支付实现(如信用卡支付)隔离开来。这样,当支付方式发生变化时,只需修改具体的支付实现,而不会影响订单处理模块。
总结:封装变化点主要针对的是交互的两端都是变化点,这时可以引入一个中间人,这个中间人负责两头变的部分,但是这个中间人本身是不变的,这样就能实现交互的两端都是独立变化的。
8. 针对接口编程,而不是针对实现编程
- 不将变量类型声明为某个特定的具体类,而是声明为某个接口;
- 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口;
- 减少系统重各部分的依赖关系,从而实现“高内聚、低耦合的类型设计方案
举例:假设有一个图形绘制程序,这个程序需要绘制不同类型的图形比如Circle和Rectangle,这时可以定义一个基类Shape,这个Shape有抽象方法draw,在客户端代码中只关注接口draw即可,无需理会传入的具体类是什么。
class Shape {
public:
virtual void draw() const = 0;
};
class Circle : public Shape{ ... }
class Rectangle : public Shape { ... }
// 客户端代码
void drawShape(const Shape& shape) {
shape.draw();
}
int main() {
Circle circle;
Rectangle rectangle;
drawShape(circle); // 客户端代码只关注接口
drawShape(rectangle); // 客户端代码只关注接口
return 0;
}
总结:在设计程序之前我们需要时刻注意客户端程序只关注到接口方面,而具体的实现交给逻辑层去做,这样能提高系统的灵活性、可扩展性以及可维护性。