为了能够更好地写出低耦合、高内聚、便于开发、测试、持续集成的成熟代码,程序员可能需要以某种框架来规范自己的代码逻辑。
设计模式可以被认为是一种在需求确认之后产生的蓝图,可以在最小的模块上应用,也可以从整体架构上实现。
设计模式有一些公认的原则,它们可能是经验性的,不一定完全严格遵循,但需要时刻注意:

  • 单一职责原则
  • 接口隔离原则
  • 依赖倒转原则
  • 里氏替换原则
  • 开闭原则
  • 迪米特法则
  • 合成复用原则

    原则

    单一职责原则

    简单来讲,就是降低耦合度,尽管在有些情况下需要重复写代码。

    接口隔离原则

    Interface Segregation,客户端不应该依赖它不需要的接口。
    也就是说,接口的抽象性使得其方法可以被任意客户端实现,但如果负责不同逻辑的客户端(类)想要只依赖某一接口的部分方法实现,而这些方法都是该接口的真子集且彼此可能存在交集,这样的情况就是接口没有隔离。 设计模式的原则和分类 - 图1非接口隔离情况
    上图,假定X通过foo依赖B;Y通过foo依赖D,B、D分别实现了foo的方法,则类图中接口没有隔离。
    问题在于,B、D其实只需要分别实现X、Y依赖的那部分方法即可,然而由于接口没有隔离,B、D不得不实现它们本不需要实现的2个额外方法。
    按照接口隔离原则,重新设计类图如下: 设计模式的原则和分类 - 图2接口隔离情况
    虽然看起来类更加复杂了,总体的耦合度却减少了。

    依赖倒转原则

    Dependency Inversion

  • 高层模块不应该依赖底层模块,二者都应该依赖其抽象(接口或抽象类)

  • 抽象不应该依赖细节,细节应该依赖抽象
  • 中心思想:面向接口编程

面向接口编程可能不是最高效的,但往往是稳定的。
比较下面两种情况:

  1. class A{
  2. public int foo(){
  3. return 1000;
  4. }
  5. }
  6. class B{
  7. // 显然,在B中显式地依赖了A,那么就会损失一部分对类C、类D等的扩展性
  8. // 如果类C、类D也是细节的实现,那么应该使用依赖倒转原则建立通用的接口
  9. public double bar(A a){
  10. return a.foo() * 0.1;
  11. }
  12. }
  13. public class Demo{
  14. public static void main(String[] args){
  15. B b = new b();
  16. b.bar(new A());
  17. }
  18. }

(过度)依赖细节的情况

  1. interface I{
  2. public int foo();
  3. }
  4. class A implement I{
  5. public int foo(){
  6. return 1000;
  7. }
  8. }
  9. class B{
  10. // 细节依赖了抽象
  11. public double bar(I i){
  12. return i.foo() * 0.1;
  13. }
  14. }
  15. class C implement I{
  16. public int foo(){
  17. return 1020;
  18. }
  19. }
  20. class D implement I{
  21. public int foo(){
  22. return 1030;
  23. }
  24. }
  25. public class Demo{
  26. public static void main(String[] args){
  27. B b = new b();
  28. b.bar(new A());
  29. b.bar(new C());
  30. }
  31. }

依赖倒转的情况

依赖关系传递的三种方式

以Java为例:

  • 接口传递
  • 构造方法传递
  • setter方式传递

    里氏替换原则

    Liskov Suvstitution
    面向对象编程中,核心在于继承,而在继承关系中,父子关系的绑定是约定式的,父子类之间的耦合使得程序的侵入性大大增强,而庞大的继承树则使得维护父类的任务变得过于复杂。里氏替换原则作为一种规范在OO编程中需要尽量满足。

  • 子类能完全满足父类的任务,即引用父类的地方必须能透明地使用其子类对象(理想情况)

  • 子类尽量不要重写父类方法
  • 在适当的情况下,通过聚合、组合、依赖(继承转移到更基础的类)避免不必要的重写和继承。

    开闭原则

    Open & Close
    编程中最基础、最重要的设计原则。
    所谓“开放”指的是扩展开放,而对修改“关闭”。
    而且开放的最终对象是类、方法;关闭的最终对象是客户端,调用等使用场景。
    另外在需求有变时,尽量通过扩展而不是修改已有代码来实现。

    编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则。

迪米特法则

  • 一个对象应该对其他对象保持最少的了解
  • 类与类的关系越密切,耦合度越大
  • Demeter Principle又称最少知道原则,尽量将逻辑封装在类的内部。
  • 只和直接的朋友(耦合关系)通信

什么是直接的朋友?
:某类的成员变量、方法参数、方法返回值中的类是该类的直接朋友;
:局部变量中的类就不是直接朋友。
=>不要轻易在局部变量中实例化外部定义的类。
私以为链式调用很好地体现了Demter Principle

合成复用原则

Composite Reuse Principle
尽量使用合成/聚合的方式,而不是使用继承。
从另一方面讲,继承是要花费成本的,use it wisely。
尤其是在依赖的方式不仅限于继承的时候,使用继承在长远看来得不偿失。

设计模式的分类

这里就把它当作目录了,中文对应的链接是理论部分的文章链接,学习记录自韩顺平图解设计模式;英文对应的链接是我对 Game Programming Patterns 一书的译文。

  • 创建型模式
    • 单例(Singleton)
    • 抽象工厂 (Abstract Factory)
    • 原型(Prototype)
    • 建造者模式 (Builder)
    • 工厂模式 (Factory)
  • 结构型模式
    • 适配器模式
    • 桥接模式
    • 装饰模式
    • 组合模式
    • 外观模式
    • 享元模式(Flyweight
    • 代理模式(Proxy)
  • 行为型模式
    • 模板方法
    • 命令模式(Command)
    • 访问者模式
    • 迭代器模式
    • 观察者模式(Observer)
    • 中介者模式
    • 备忘录模式
    • 解释器模式(Interpreter)
    • 状态模式
    • 策略模式
    • 责任链模式