面向对象设计原则
单一职责原则(SRP)
一个类应该只负责一项职责,即只承担某一项功能。一个类应该仅有一个引起它变化的原因,变化的方向隐含着类的责任。当需求变更后,职责 A 可能会分化为粒度更细的职责 A1 和 A2,此时单一职责原则就被破坏了。
开放封闭原则(OCP)
对扩展开放,对更改封闭。
类模块应该是可以扩展的,但是不可修改。
- 将相同的变化封装到一个接口或抽象类中;
- 将不同的变化封装到不同的接口或抽象类中。
里氏替换原则(LSP, Liskov)
派生类必须能够替换它们的基类(IS-A)。如果对每一个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都换成 o2 时,程序 P 的行为没有变化,那么类型 T2 是类型 T1 的派生类。
- 派生类可以实现基类的抽象函数,派生类可以扩展父类的功能;
- 派生类不能改变基类原有的功能;
- 派生类重载基类的函数时,派生类函数的参数要比基类函数的参数范围更宽松。
- 派生类实现基类的抽象函数时,派生类的返回值要比基类的返回值范围更严格。
依赖倒置原则(DIP)
高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定)。
抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)。
- 低层模块应该尽量有抽象类或接口;
- 变量的声明类型尽量是抽象类或接口;
- 使用继承时必须遵循里氏替换原则。
接口隔离原则
如果类 A 通过接口 I 依赖类 B,类 C 通过接口 I 依赖类 D,接口 I 中有f1、f2、f3、f4、f5共5种方法,类 B 是其中f1、f2、f3方法的实现,类 D 是其中 f1、f4、f5方法的实现,此时若将 I 拆分为三个不同的接口,即 A、B 共依赖的接口 I0(f0),A 单独依赖的接口 Ia(f2、f3),B 单独依赖的接口 Ib(f4、f5),则遵循了接口隔离原则。
- 不应该强迫客户程序依赖它们不用的方法;
- 接口应该小而完备。
组合/聚会复用原则
类继承通常为“白箱复用”,对象组合通常为“黑箱复用”。
继承在某种程度上破坏了封装性,子类父类耦合度。
对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。
最少知道原则
一个类对自己依赖的类知道得越少越好。
只与自己的直接朋友通信,使类与类之间保持较低的耦合关系,降低系统的耦合度。
- 每个类应该尽量降低其数据成员与成员函数的访问权限;
- 只有有可能,一个类应当设计成不变类;
- 一个类对其他类的引用应该尽可能降到最低。
封装变化点
使用封装来创建对象间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。
针对接口编程,而不是针对实现编程
不将变量类型声明为某个特定的具体类,而是声明为某个接口。
客户程序无需获知对象的具体类型,只需要知道对象所具有的接口。
减少系统中各部分的依赖关系,从而实现“高类聚,松耦合”的类型设计方案。
Template Method
模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
使用场景:
- 在某些类的算法中,用了相同的方法,造成代码的重复;
- 控制子类扩展,子类必须遵守算法规则。
Strategy
策略模式:将业务逻辑或算法封装到一个类(Context)中,Context 类中聚合有抽象策略类(Strategy),Strategy 的派生类(ConcreteStrategy)实现不同的算法。
使用场景:
- 许多相关的类仅仅是行为有所差异;
- 需要使用一个算法的不同变体;
- 算法使用客户不应该知道的数据;
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。
Observer
观察者模式:定义了一种一对多的依赖关系,让多个观察者对象(Observer类)同时监听某一主题对象(Subject类)。这个主题对象在状态发生变化时,会通知(Notify方法)所有观察者对象,使他们能够自动更新(Update方法)自己。主题类以容器维护观察者类对象的引用,可以添加与移除。Notify 函数与 与 Update 函数分别在主题类与观察者类的派生类中实现。
使用场景:
- 一个对象的改变需要同时改变其他对象,并且它不知道具体有多少对象有待改变时;
- 一个抽象模型有两个方面,其中一方面依赖于另一方面,为了将这两者封装在独立的对象中使他们各自独立地改变和复用。
Decorator
装饰模式:动态地给一个对象添加一些额外的职责。Component 类作为抽象类,其派生类 ConcreteComponent 负责实现具体功能。为了增加额外的职责,以装饰抽象类 Decorator 继承自 Component,同时聚合有 Component 类对象的引用,Decorator 的派生类负责实现添加的功能。
使用场景:
- 主题类需要在多个方向上扩展功能;
- 需要把所需的功能按正确的顺序串联起来进行控制;
- 需要在内部组装完成再显示出来的情况;
Bridge
桥接模式:将抽象部分(Abstraction)与它的实现部分(AbstractionImplement)分离,使他们都可以独立地变化。在 Abstraction 类中聚合有一个 AbstractionImplement 类的引用。RefineAbstraction 类继承自抽象部分以实现抽象部分的功能,ConcreteImplementor 类继承自实现部分来实现这部分的功能。
使用场景:
- 一个对象有多个变化因素;
- 多个变化因素在多个对象间共享时,考虑将这部分变化的部分抽象出来再聚合;
- 一个对象的多个变化因素可以动态变化,为了使之分别独立变化。
参考:###
Erich Gamma,Richard Helm,Ralph Johnson 等.《设计模式:可复用面向对象软件的基础》( 刘建中 等 译).