Skip to content

6原则和23模式简介

Design Pattern

计算机领域设计模式的概念引入自建筑设计领域

设计模式简述

设计模式是软件设计领域对普遍(反复出现)问题提出的典型解决方案,每一个设计模式就像是某类问题的蓝图,可以解决逻辑开发中遇到的共性问题, 使用设计模式是为了编写出更优秀的代码,这些代码有着高内聚、低耦合、可扩展和可复用的特点。

内聚、耦合: 衡量模块独立程度的标志量,内聚表示模块内元素的紧密程度、耦合表示各模块间的独立程度, 高内聚、低耦合表示模块越独立,独立的模块更容易开发、测试和维护。

目前主流的设计模式主要分为 6 种设计原则、 3 大类 23 种模式。

6 种设计原则:

  • 单一职责原则
  • 最少知道原则(迪米特法则)
  • 里氏替换原则
  • 接口隔离原则
  • 依赖倒置原则
  • 开闭原则

23 种模式:

  • 创建模式:提供创建对象的机制,提升已有代码的灵活性和可复用性。
    • 工厂模式 Factory
    • 抽象工厂模式 Abstract Factory
    • 建造者模式 Builder
    • 原型模式 Prototype
    • 单例模式 Singleton
  • 结构模式:介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效。
  • 行为模式:负责对象间的高效沟通和职责传递委派。

设计原则

单一职责原则

单一职责原则的定义:
There should never be more than one reason for a class to change.
一个类,应当只有一个引起它变化的原因;即一个类应该只有一个职责。

单一职责原则(Single Responsibility Principle,SRP):是面向对象 5 个基本原则(SOLID)之一。它规定一个类应该只有一个发生变化的原因。 在 javascript 中,SRP 经常也被用到对象或方法的级别中,例如规定一个方法只做一件事。

定义解释:
SRP 的"职责"就是发生变化的原因,在 SRP 中,不允许多职责,也就是一个类(对象或者方法)不能有多种发生变化的原因。 这是因为在编程中,类(对象或者方法)的变化则意味着改写代码,改写代码通常是危险的不稳定的,比如:有一个方法将多个功能代码全部耦合到一起,那么随着业务的变更和复杂, 将有可能对其中某个功能进行调整和优化,那么此时势必会变更代码逻辑,则很有可能影响其他功能的代码。在需求的变迁过程中,如果一个类(对象或者方法)承担过多的职责,需要改写它的可能性就越大。 SRP 的主要意义就是避免这种问题的发生,保证了我们的代码是高内聚、低耦合并且是可持续维护的。

注意点:

  1. SRP 原则最核心的就是分离职责,但是并不是所有的职责都应该被分离。如果随便需求变化,两个职责总是紧密联系与同步变更,那么就不需要分离。
  2. 对于已经被耦合的职责,如果还没有发生改变的征兆,那么先不必分离,代码重构时再做分离。

对于 SRP 原则的误解:
根据 SRP 原则,我们需要正确地分离职责,但是在实际开发中,我们需要思考要不要一成不变的遵守原则,因为在各种优秀的框架中,违反 SRP 的例子也不少见。 比如 JQuery.attr 方法同时可以处理取值以及赋值的逻辑,这对于 JQuery 的维护者来说会带来一些困难,但是对于使用者,则简化了很多。 所以,使用 SRP 时,在易用性和规范性之间要有一些取舍。对于实际开发而言,没有标准答案,开发者需要根据自身的场景以及问题去思考和取舍。

SRP 的优缺陷点:
优点:降低单个类(对象或方法)的复杂度,按照职责把对象分成更小的粒度,这有助于代码复用和单测。且当一个职责变更时,不会影响其他职责。
缺点:明显增加代码编写的复杂度,并且当我们把目标分成更小的粒度以后,也变相增加了这些目标相互关联的复杂度(对象耦合度)。

开闭原则

开闭原则的定义:
Software entities should be open for extension,but closed for modification.
一个软件实体应当对扩展开放,对修改关闭。

开闭原则(Open-Closed Principle,OCP):软件实体(类、模块、函数)应该是可扩展的,但是不可修改。

定义解释:
根据 OCP,在设计软件实体时需要保证模块在不被修改的前提下是可扩展的。这里的修改是指修改源代码,则应当是在不修改源码的情况可以改变模块的行为。 在面向对象设计中,OCP 是最基础也是最重要的一条原则,OCP 可以说是其他原则的具体形态或者编写代码的目标,而其他原则都是达到这个目标的过程和工具。 一个优秀的程序,往往说明它是符合开闭原则的。

最少知道原则

最少知道原则(Least Knowledge Principle,LKP)也叫迪米特法则(Law Of Demeter,LoD):意思是一个软件实体应该尽可能少的与其他实体发生相互作用。

定义解释: LKP 的软件实体可以是系统、模块、类、对象、函数、变量等。这条法则实际上是很多著名系统,例如火星登陆软件系统、木卫二轨道飞船软件系统的指导设计原则。 LKP 要求我们在设计程序时,尽量减少两个软件实体之间的相互作用,如果两个实体不直接联系,那么最好的方法就是引入第三者,A 与 C 之间的通信可以由 B 转发请求。 A(或者其他所有实体)与 C 的通信都由 B 转发,这样就降低了实体之间的关联,降低了耦合度。LKP 的核心思想就是软件实体之间的解耦、弱耦合,只有解耦或者弱耦合以后,复用率才能提高。

里氏替换原则

里氏替换原则的两种定义:

第一种定义:
If for each object o1 of type S there is an object o2 of type T such that for all programs Pdefined in terms of S,the behavior of P is unchanged when o1 is substituted for o2 then T is asubtype of S.
这个定义是最正宗的定义,意思是:如果对一个类型为S的对象o1,都有类型为T的对象o2,使得以S定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T是类型S的子类型。

第二种定义:
Functions that use pointers or references to base classes must be able to use objects ofderived classes without knowing it.
第二个定义意思是:所有引用基类的地方必须能透明地使用其子类对象。

里氏替换原则(Liskov Substitution Principle,LSP):这里使用第二中容易理解的定义来,意思是程序中所有引用基类的地方必须能透明地使用其子类对象。

定义解释: LSP 原则主要说明了 只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常, 使用者可能根本就不需要知道父类还是子类。如果反过来则不可以,有子类的地方,父类未必就能适应。 也可以理解为子类可以扩展父类的功能,但是不能改变父类的功能,如果子类继承时改变了父类的功能,那么用子类替换父类后,则可能产生异常。 LSP 原则的主要目的是规定良好使用继承的规范,继承机制虽然是面向对象核心的优秀的机制,但也不可避免存在缺陷。 比如子类继承父类全部的属性和方法,当父类的属性和方法发生需要修改时,就必须考虑子类继承的稳定性,这也是变相增加了耦合性。 所以 LSP 正是为了克服多个类存在继承关系时重写父类可复用性变差和扩展父类引入新错误的原则。

LSP 规范:

  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  2. 子类可以有自己的个性(可以扩展新的属性和方法)。
  3. 覆盖或实现父类方法时参数可以放大(参数的范围:父类 F,子类 S,F <= S)。
  4. 覆盖或实现父类方法时输出结果可以被缩小(返回值范围:父类 T,子类 S,T >= S)。

依赖倒置原则

依赖倒置原则英文定义:
High level modules should not depend upon low level modules. Both should depend uponabstractions. Abstractions should not depend upon details. Details should depend uponabstractions.

依赖倒置原则(Dependence Inversion Principle,DIP):高层模块不应该依赖低层模块,两者都依赖其抽象。抽象不应该依赖具体,具体应该依赖抽象。

接口隔离原则

接口隔离原则英文定义:

第一种定义:Clients should not be forced to depend upon interfaces that they don't use.
意思是:客户端不应该依赖它不需要的接口。

第二种定义:The dependency of one class to another one should depend on the smallest possibleinterface。
意思是:类间的依赖关系应该建立在最小的接口上。

接口隔离原则(Interface Segregation Principle,ISP):一个类对另外一个类的依赖性应当是建立在最小的接口上的; 一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。因此使用多个专门的接口比使用单一的总接口要好; 不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构,即不要强迫客户使用它们不用的方法,否则这些客户就会面临由于这些不使用的方法的改变所带来的改变。