设计模式 - 六大原则

1,159 阅读7分钟

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

核心思想

就一个类而言,应该仅有一个引起其变化的原因。

问题描述

假如有类Class1完成两个职责T1、T2,当职责T1或T2有变更需要修改时,有可能影响到该类的另一个职责的正常工作。

具体描述

如果一个类承担的职责过多,就等于把这些职责耦合在一起,导致其一个职责的变化将可能会影响到其它职责的完成(削弱或抑制这个类完成其它职责的能力)。这种耦合将导致设计变得脆弱,当变化发生时,设计会遭到意想不到的破坏。

好处

降低类的复杂性,提高可读性、可维护性和扩展性,以及降低因为变更引起的风险。

注意

  1. 软件设计真正要做的许多内容,就是发现职责并把这些职责相互分离。
  2. 单一职责提出了一个编程的标准,就是用“职责”和“变化”来衡量接口与类设计是否优良。但这两者都无法直接度量,需要依据具体项目和环境。

2. 里氏代换原则(LSP, Liskov Substitution Principle)

核心思想

子类型必须能够替换掉它们的父类型(基类)。也就是在任意使用父类型的地方,可以将这个父类型替换成其任意子类型。

具体描述

一个软件实体,如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是在程序中,在使用父类的地方,将父类替换成其子类,程序的行为没有变化。 也正是这个原则,才使得面向对象的继承复用成为可能。只有子类可以替换掉父类,软件单位的功能不受影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。

好处

增强了程序的健壮性,子类之间不会相互影响,增加新的子类,原有的子类仍正常工作。

注意

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

3. 依赖倒转原则(DIP,Dependence Inversion Principle)

核心思想

(依赖) 抽象不应该依赖细节,细节应该依赖于抽象。也就是要针对接口编程(核心),而非针对实现编程。 (倒转) 高层模块不应依赖低层模块,应该依赖于抽象。

  • 高层模块 -> 调用端
  • 低层模块 -> 具体实现类
  • 抽象 -> 接口或抽象类
  • 细节 -> 实现类

问题描述

假设一开始类A依赖与类B,现在要改为类A依赖于类C,那么就需要改动类A的代码。这里的类A是高层模块,类B和类C是低层模块;高层模块一般负责复杂的业务逻辑,底层模块实现具体的原子操作。修改高层模块会给程序带来风险。

问题举例

User模块用来处理用户数据,原先它依赖于SQLServer类,现在业务变动,要求使用MySQL类,你需在User模块中修改所有使用SQLServer的地方。User模块在这里属于高层模块,SQLServer与MySQL类是低层模块。

解决方法

对低层模块进行抽象,高层模块依赖于低层模块的抽象。也就是对SQLServer类与MySQL类进行抽象,创建一个SQL接口或抽象类,User模块依赖于这个SQL接口或抽象类进行编程。

其它描述

相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构稳定的多。

注意

在实际编程中,一般要做到:

  1. 低层模块尽量要有抽象类或接口。
  2. 变量的声明类型应尽量使用抽象类或接口名。
  3. 使用继承时,遵循里氏代换原则。

4. 接口隔离原则(ISP,Interface Segregation Principle)

核心思想

客户端不应该强行依赖它不需要的接口,类与类之间的依赖关系应该建立在最小的接口上。

思想描述

也就是在程序中,接口的方法应尽量少,不要使接口过于臃肿,不要有很多不相关的逻辑。尽量为每个类建立专用接口,而不是多个类使用同一个接口,导致这个接口中的方法很多。

注意

  1. 接口尽量小,但是有限度。 对接口进行细化可以提高程序设计的灵活性,但是如果过小,将导致接口数量过多,增加了复杂度。
  2. 提高内聚,减少对外交互。 使接口用最少的方法,完成最多的事情。
  3. 为依赖接口的类定制服务。 只暴露给调用类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。

5. 迪米特法则(LoD,Law of Demeter)

核心思想

类间解耦。

思想描述

也叫最少知识原则(LKP,Least Knowledge Principle),如果两个类不必彼此之间直接通信,那么这两个类就不应该发生直接的相互作用。如果一个类需要调用另一个类的方法,可以通过第三者来转发这个调用。 一个模块设计的好坏,一个重要标志就是该模块在多大的程度上将自己内部的数据与实现有关的细节隐藏起来。信息的隐藏非常重要的原因在于,它可以使各个子系统之间解耦,从而允许它们独立地被开发、优化、使用阅读及修改。 迪米特法则首先强调的前提是,在类的设计上,每一个类都应该尽量降低成员的访问权限。也就是尽量使用private,不需让其它类知道自己的属性和行为。 迪米特法则的根本思想是,强调了类之间的松耦合。类之间的耦合越弱,越利于复用。一个处于弱耦合的类被修改,不会对其有关的其它类有影响。 迪米特法则通常被描述为:Only talk to your immediate friends(只和你最近的朋友进行交互)。每个对象都会与其它对象之间有耦合关系,这种耦合关系就是这种“朋友”关系。耦合的方式有多种,如依赖、关联、聚合、组合等,我们称出现在成员变量、方法参数、方法返回值中的类为最近的朋友,而出现在局部变量中的类则不是最近的朋友。

示例

去店里买东西,我们将钞票拿给收银员,而非是将钱包丢给收银员。

相关设计模式

  1. 外观模式(Facade)
  2. 中介者模式(Mediator)

注意

程序设计的总原则就是:高内聚、低耦合。

6. 开放-封闭原则(OCP,Open Close Principle)

核心思想

软件实体(类、模块、函数等)应该能够扩展,但不可以修改。

具体描述

尽量通过扩展软件实体来解决业务需求的变动,而不是修改已有代码。 两个特征:

  1. 对于扩展是开放的(Open for extension)
  2. 对于更改是封闭的(Close for modification)

注意

开放-封闭原则是面向对象设计的核心所在。 遵循这个原则可以带来面向对象技术的巨大好处:可维护、可扩展、可复用、灵活性好。 应该仅对程序中出现频繁变化的部分进行抽象,但决不要对每个部分进行抽象。拒绝不成熟的抽象与抽象本身一样重要。

总结

  • 单一职责原则:实现类的职责要单一。
  • 里氏代换原则:不要破坏继承体系。
  • 依赖倒转原则:面向接口(抽象)编程。
  • 接口隔离原则:接口设计需要精简。
  • 迪米特法则:降低类之间的耦合。
  • 开闭原则(总纲):对扩展开放,对修改关闭。

设计模式的6大原则,我们可以根据业务情况灵活遵守,不能生搬硬套,过犹不及,只要是在一个合理的遵守程度上,就算是良好的设计。