从今天开始,计划把设计模式整理一下,主要是学习 秦小波的《设计模式之禅》中的内容,加上自己的一点想法。

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

定义

应该有且仅有一个原因引起类的变更(There should never be more than one reason for a class to change)

优点

  • 类的复杂性降低,实现什么职责都有清晰明确的定义;
  • 可读性提高,复杂性降低,那当然可读性提高了;
  • 可维护性提高,可读性提高,那当然更容易维护了;
  • 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助,单一职责适用于接口、类,同时也适用于方法

缺点

  • 关于职责的定义比较模糊,没有量化的标准

最佳实践

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

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

定义

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

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

即:只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应

里氏替换原则为良好的继承定义了一个规范,包括一下四点

  1. 子类必须完全实现父类的方法
  2. 子类可以有自己的个性
  3. 覆盖或实现父类的方法时输入参数可以被放大(就是输入参数的类型宽于父类的类型覆盖范围)
  4. 覆写或实现父类的方法时输出结果可以被缩小

最佳实践

在项目中,采用里氏替换原则时,尽量避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难调和了,把子类当做父类使用,子类的“个性”被抹杀——委屈了点;把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离——缺乏类替换的标准

依赖倒置原则(Dependence Inversion Principle 简称DIP)

每一个逻辑的实现都是由原子逻辑组成的,不可分割的 原子逻辑就是低层模块,原子逻辑的再组装就是高层模块。抽象就是指接口或抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化

定义

  • 高层模块不应该依赖低层模块,两者都应该依赖其抽象;
  • 抽象不应该依赖细节;
  • 细节应该依赖抽象。

依赖倒置原则在PHP语言中的表现为

  • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
  • 接口或抽象类不依赖于实现类;
  • 实现类依赖接口或抽象类

优点

采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风 险,提高代码的可读性和可维护性

依赖的三种实现方式

  1. 构造函数传递依赖对象,在类中通过构造函数声明依赖对象,按照依赖注入的说法,这种方式叫做构造函数注入,按照这种方式的注入
  2. Setter方法传递依赖对象,在抽象中设置Setter方法声明依赖关系,依照依赖注入的说法,这是Setter依赖注入,按 照这种方式的注入
  3. 接口声明依赖对象,在接口的方法中声明依赖对象,该方法也 叫做接口注入。

最佳实践

  1. 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
  2. 变量的表面类型尽量是接口或者是抽象类
  3. 任何类都不应该从具体类派生
  4. 尽量不要覆写基类的方法(如果基类是一个抽象类,而且这个方法已经实现了,子类尽量不要覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性会产生一定的影响)
  5. 结合里氏替换原则使用

接口隔离原则(Interface Segregation Principle 简称ISP)

定义

客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上
客户端依赖它需要的接口,客户端需要什么接口就提供什么接口,把不需要的接口剔除掉,那就需要对接口进行细化,保证其纯洁性
接口隔离原则与单一职责的审视角度是不相同的,单一职责要求的是类和接口职责单一,注重的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少,根据接口隔离原则拆分接口时,首先必须满足单一职责原则
接口隔离原则是对接口进行规范约束,其包含以下4层含义:

  1. 接口要尽量小
  2. 接口要高内聚
  3. 定制服务
  4. 接口设计是有限度的

    最佳实践

    一个接口只服务于一个模块或业务逻辑;
    通过业务逻辑压缩接口中的public方法;
    已经被污染的接口,尽量去修改,如风险大,则采用适配器模式转化处理;
    了解环境,拒绝盲从。

迪米特法则(Law of Demeter 简称LoD)

也称为最少知识原则(Least Knowledge Principle,LKP)
一个对象应该对其他对象有最少的了解
只和朋友交流,出现在成员变量、方法的输入输出参数中的类称为朋友类,方法体内的不算。类与类之间的关系是建立在类间的,而不是方法间,因此一个方法尽量不引入一个类中不存在的对象。
朋友间也是有距离的,一个类公开的public属性或方法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。因此设计时需要反复衡量是否可以减少public方法或属性。
是自己的就是自己的,如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。
谨慎使用Serializable

开闭原则(Open Closed Principle 简称OCP)

定义

定义:一个软件实体如类、模块、和函数应该对扩展开放,对修改关闭。
前面五个原则就是指导设计的工具和方法,而开闭原则才是其精神领袖。

重要性:

扩展操作,避免修改单元测试,及回归测试;
提高代码复用性,缩小逻辑粒度,直到一个逻辑不可再拆分为止;
提高可维护性;
面向对象开发的要求。

使用

抽象约束
通过接口或抽象类约束扩展,对扩展进行限定,不允许出现在接口或抽象类不存在的public方法;
参数类型、引用对象尽量使用接口或抽象类,而不是实现类;
抽象层尽量保持稳定,一旦确定不允许修改。
元数据控制模块行为,通过扩展一个子类,修改配置文件完成业务变化,如依赖注入;
制定项目章程;
封装变化,找出预计有变化或不稳定的点,为这些变化点创建稳定的接口
将相同变化封装到一个接口或抽象类中;
不应该有两个不同的变化出现在同一个接口或抽象类中