设计六大准则(设计模式之禅)

单一职责原则 (Single Responsibility Principle)(SRP)

定义: 应该有且仅有一个原因引起类的变更。

操作:

  1. 当一个类的几个职责之间没有相互依赖时,可以考虑将职责进行拆分。
  2. SRP原则适用于接口、类,同时也适用于方法,一个方法尽可能的做一件事。

好处:

  1. 降低类的复杂度,实现什么职责都有明显的定义
  2. 可读性提高、可维护性强
  3. 变更风险低

对于单一职责原则,接口一定要做到单一职责,类的设计尽量做到只要一个原因引起变化。

里氏替换原则(Liskov Substitution Principle)(LSP)

继承的优点

  1. 代码共享,每个子类拥有父类的方法和属性;
  2. 提高代码的重用性;
  3. 提高代码的扩展性,可 “随意” 重写父类方法;

继承的缺点:

  1. 继承具有入侵性,实现父类就必须拥有父类的方法和属性;
  2. 降低代码的灵活性,原因同上;
  3. 增强了耦合性。父类中的常量、变量、方法修改后,影响子类。

定义:

凡是引用父类的地方都可以透明的使用其子类对象,反之,不成立。

引申义:

  1. 子类必须完全实现父类的方法

在类中调用其他类时 务必使用父类或接口,如果不能使用父类或接口,说明类的设计已经违反了 LSP 原则。

如果子类不能完整的实现父类中的方法或者父类中的某些方法在子类中已经发生畸变,则建议断开父子继承关系,采用依赖、聚合、组合等关系代替继承。

  1. 子类可以有自己的方法。这也就是 LSP 原则不能反过来用的原因。

  2. 覆盖或实现父类方法时,输入参数可以被放大。

  3. 覆盖或实现父类方法时,输出结果可以被缩小。

优点:

里氏替换原则的目的是增强程序的健壮性,即使增加子类,原有的子类还可以继续运行。在实际项目中,每个子类对于不同的业务含义,使用父类作为参数,传递不同的子类完成不同的业务逻辑。

依赖倒置(Dependence Inversion Principle)(DIP)

依赖倒置含义:

  1. 高层模块不应该依赖低层模块,两者都应该依赖抽象。
  2. 抽象不应该依赖细节。
  3. 细节应该依赖抽象。

具体含义:

低层模块:基本逻辑

高层模块:基本逻辑的组合

抽象:接口或抽象类

细节:实现类、继承子类

模块之间的关系通过抽象产生,实现类之间不能直接发生依赖关系,其依赖关系通过上层的接口或者抽象类产生。

接口或抽象类不依赖实现类,实现类依赖接口或抽象类。

优点:

采用依赖倒置原则可以减少类间的耦合关系(更准确的说是(通过抽象类之间的关系)降低实现类之间的关系),提高系统的稳定性,降低开发风险,提高代码的可读性和维护性。

抽象是对实现的约束,对依赖者而言,也是一种契约,不仅约束自己,同时还约束自己与外界的关系,其目的是保证所有的而细节不脱离契约的范畴,确保约束的双方按照既定的契约(抽象)共同发展,只要抽象这根基线存在,细节就不会超出范围。

依赖关系

依赖关系的概、具体表现、UML 图例表示可以参见 Java 对象间关系以及 UML 类图表示方法 下面简写如下:

  1. 构造器函数传递依赖对象
  2. 方法中参数传递对象
  3. 接口声明中声明依赖对象。

最佳实践:

依赖倒置原则的本质是 通过抽象(抽象类或接口类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合,如何做:

  1. 每个类尽量都有接口或抽象类,这是依赖倒置原则的基本要求,有了抽象才有可能倒置
  2. 变量的表面类型(编译类型)尽量是接口或抽象类。
  3. 任何类都不应该从具体类派生出来。
  4. 尽量不要覆盖基类的方法(该方法已经在基类中实现)。如果说基类是一个抽象类,而且这个方法已经被实现了,子类尽量不要覆写。类间的依赖是抽象,覆写类抽象方法,对依赖的稳定性有所影响。
  5. 集合里氏替换原则。

接口隔离原则

再此层面上 接口分为:

  1. 实例接口 (类也是一种接口)
  2. 类接口(interface)

隔离:即把一个接口中的多种职责进一步拆分出来,作为两个颗粒度更小的接口,此刻两个职责被单独隔离处理。

定义:

接口尽量细化,同时接口中的方法尽量少。

如何对接口进行约束:

  1. 接口尽量小,满足单一职责原则。
  2. 接口高内聚。

针对于接口高内聚就是要在接口中尽量少公布 public 方法,接口是对外的承诺,对外承诺越少越有利于系统的开发,这样变更的风险越低。

迪米特法则(最少知识原则)(Least Knowledge Principle,LKP)

一个对象应该对其他对象有最少的了解,换句话说,一个类应该对自己需要耦合或调用的类知道的最少,被耦合的类或这被调用的类内部如何复杂都和该类没有关系,我只需知道它们提供的 public 方法。

只需要和朋友类联系:

朋友类:出现在成员变量、方法的输入和输出参数的类为朋友类。
其实朋友类就是产生 依赖关系的类

朋友类中不应该向对方暴露的过多

如果可能的话,尽量向其他类暴露尽量少的方法。

自己的自己拥有
如果一个类可以放在本类中,既不会增加类间关系,也不会对本类产生负面影响,那就放在本类中。

尽量少的使用 Serializable

开闭原则

一个软件实体如类、模块、函数应该对扩展开发,对修改关闭。

开闭原则是最基础的一个原则,前面介绍的 5 个原则都是在开闭原则的具体形态,它们为指导设计的工具和方法,而开闭原则才是精神领袖。

原则细节

应该尽量通过扩展实体行为的方式来实现变化,而不是通过修改已有的代码来实现变化。

在软件开发中,尽可能减少代码粒度,代码粒度越小,被复用的可能越大。

如何使用

  1. 抽象约束。

具体表现为:

1. 在对接口或抽象类进行扩展时,不允许出现接口或抽象类中不存在的 public 方法。
2. 参数类型、引用对象尽量使用接口或抽象类,而不是实现类。
3. 抽象应保持稳定,一旦确定不允许更改。
  1. 封装变化

含义:
1. 将相同的变化封装到接口或抽象类中
2. 将不同的变化封装到不同的接口或抽象类中,不应该两个变化出现在同一个接口或抽象中。