面向对象设计模式温故知新

设计模式(Design Pattern)代表了面向对象程序设计的最佳实践,是一套软件工程化背景下,用于提高代码可复用性的解决方案。设计模式这一术语起源于 1995 年《设计模式 - 可复用面向对象软件的基础》一书的出版,该书 4 位作者被称为四人帮 GoFGang of Four,全书一共收录了常用的 23 种模式,该书的出版是软件工程领域的一座里程碑,标志着 Java C++ 等面向对象的程序设计语言,迈向了更加具有标准范式的工程化方向。

面向对象的编程思想,通过封装继承多态降低代码的耦合度,而设计模式在此基础上强调了代码的可复用性。本文代码采用 C++ 语言进行描述,然后搭配 UML 示意图进行描述。

简单介绍

GoF 著作中描述了 23 种设计模式,并将其总体归为 3 类。

设计模式 3 种分类

  1. 创建型:抽象了对象实例化过程,用来帮助创建对象的实例。
  2. 结构型:描述如何组合类和对象以获得更大的结构。
  3. 行为型:描述算法和对象间职责的分配。

23 种设计模式概览

  1. 简单工厂GoF 著作无):提供一个创建对象实例的功能,而无须关心具体实现,被创建实例的类型可以是接口、抽象类、也可以是具体类。
  2. 外观模式结构型):为子系统中的一组接口提供一个一致的界面,该模式定义了一个高层接口,使子系统更加易用。
  3. 适配器模式结构型):将一个类的接口转换成客户希望的另外一个接口,该模式使得原本由于接口不兼容而不能一起工作的类可以一起工作。
  4. 单例模式创建型):保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  5. 工厂方法模式创建型):定义一个用于创建对象的接口,让子类决定实例化哪一个类,该模式使一个类的实例化延迟到其子类。
  6. 抽象工厂模式创建型):提供创建一系列相关或依赖的对象的接口,而无需指定它们具体的类。
  7. 生成器模式创建型):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
  8. 原型模式创建型):用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
  9. 中介者模式行为型):用一个中介对象来封装一系列的对象交互,该模式使得各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
  10. 代理模式结构型):为其它对象提供一种代理,以控制对该对象的访问。
  11. 观察者模式行为型):定义对象间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
  12. 命令模式行为型):将一个请求封装为一个对象,从而可以使用不同请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
  13. 迭代器模式行为型):提供一种方法顺序访问一个聚合对象中的各个元素,而又不需暴露该对象的内部表示。
  14. 组合模式结构型):将对象组合成树形结构,以表示“部分-整体”的层次结构,该模式使得用户对单个对象和组合对象的使用具有一致性。
  15. 模板方法模式行为型):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,该模式使得子类可以不改变一个算法的结构即可重定义该算法某些特定步骤。
  16. 策略模式行为型):定义一系列算法,将其分别封装,并使其相互可以替换,该模式使得算法可独立于其客户而变化。
  17. 状态模式行为型):允许对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
  18. 备忘录模式行为型):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样,以后就可将该对象恢复到原先保存的状态。
  19. 享元模式结构型):运用共享技术有效地支持大量细粒度的对象。
  20. 解释器模式行为型):给定一个语言,定义其方法的一种表示,并定义一个解释器,该解释器用来解释语言中的句子。
  21. 装饰模式结构型):动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活。
  22. 职责链模式行为型):使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
  23. 桥接模式结构型):将抽象部分与它的实现部分分离,使它们都可以独立地变化。
  24. 访问者模式行为型):表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

设计原则

设计原则是面向对象分析设计的指导思想。设计模式只是特定场景下的实现手段。换而言之,设计模式是设计原则的具体体现

单一职责原则Single Responsibility Principle):一个类应该仅有一个引起它变化的原因。 这里变化的原则就是所谓的“职责”,如果一个类有多个引起它变化的原因,就意味着这个类有多个职责,即将多个职责耦合在一起,职责间容易相互影响,一个职责的变化会影响到其它职责的实现。

开放-关闭原则Open-Closed Principle):一个类应该对扩展开放,对修改关闭,简称为开闭原则。 开闭原则要求类的行为可以在不修改已有代码的情况下进行扩展,实现开闭原则的关键在于合理的抽象,分离变化与不变的部分,并为变化部分预留扩展方式(如继承、钩子方法,动态组合对象)。

里氏替换原则Liskov Substitution Principle):子类型必须能够替换掉它们的父类型。 这是一种明显的多态的应用,当一个类继承了另一个类,子类就拥有父类中可以继承下来的属性和方法,此时子类型是可以替换掉父类的。但是当子类覆写了父类的方法和属性时,使用子类替换父类时会产生错误,因此里氏替换要求子类必须能够替换掉父类。

依赖倒置原则Dependence Inversion Principle): 要依赖于抽象,而不要依赖于具体的类。 比较典型的做法是:高层模块不应该直接依赖底层模块,二者都应依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。底层接口的需求应该由高层提出(抽象),然后由底层(依照抽象)进行实现,即底层接口的所有权在高层模块,即倒置所有权。

接口隔离原则Interface Segregation Principle):不应该强迫客户端依赖它们不需要使用的方法。 该原则通常用于处理比较庞大的接口,这类接口通常有较多的方法声明,涉及到许多职责。客户端在使用这类接口的时候,会被许多冗余方法污染接口。这类接口设计时应该按不同客户端需要进行职责分离,只包含客户端职责所需要的方法。

最少知识原则Least Knowledge Principle):尽量减少对象之间的交互,对象只和自己的朋友类交互。 最少知识原则指导的朋友类有:当前对象本身、通过方法参数传入的对象,当前对象创建的对象,当前对象实例变量所引用的对象、方法内创建或实例化的对象。该原则要求方法调用必须保持在一定的界限范围,尽量减少对象间的依赖,松散对象间的耦合。

设计原则是一种建议指导,实际开发中很少能做到完全遵守,因此应避免过度设计。