下文中,我们将方法、类、模块统称为“实体”。

SRP单一职责原则

Single-Resposibility Principle,单一职责原则。
一个实体,它应该只处理其所在抽象层级上的一件事,只解决/回答一个问题。

单一职责的好处

  • 降低复杂度,提高可维护性;
  • 提高组件的复用性。

    思考:SRP与接口的多实现冲突吗?

    不冲突。因为接口层面为了保证接口隔离原则ISP,会互相切分的比较细,内聚程度不够高,单一接口的实现并不足以完成某一逻辑或功能,对多个接口进行实现,它们合起来完成了类层面的“一件事”,因此不违反SRP。

    OCP开放封闭原则

    Open-Closed principle,开放封闭原则。
    字面意思——软件实体应该是可扩展的,而不可修改的;也就是,对扩展开放,对修改封闭的。
    白话就是——当有了新的业务需求时,不应该通过修改已有代码来实现目标,而应该通过扩展已有代码(添加新的接口、新的实现类等)。

    核心思想

    实现开开放封闭原则的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。 让类依赖于固定的抽象,所以修改就是封闭的; 而通过面向对象的继承和多态机制,又可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以就是开放的。

    开放封闭的好处

    代码拥有更强的可扩展性和可维护性。

    实现方案

    模板方法模式、策略模式。

    LSP里氏替换原则

    Liskov-Substituion Principle,里氏替换原则。子类型必须能够替换其基类型。
    直白点,就是任何使用基类类型的地方(如调用方法时的入参)都能替换成其子类类型,而不会出现意想不到的错误。
    如果违反了LSP,往往就会违反OCP:若方法 M 有一个类型为类 B 的参数,如果类 B 的子类没有遵守 LSP,在调用方法 M 时传入了一个类 B 的子类,M 会出错。此时,为了不出错,方法 M 势必要对 B 的子类作特殊处理(if…else…),这就违反了OCP。

    里氏替换原则的好处

  • 实现 OCP 的重要保障之一;

  • 降低继承带来的复杂度,继承只能扩展基类的功能,而非『篡改』(可以无差别的对待基类及其所有子类),这就保证了继承和复用的可靠性;
  • 在决定使用继承前,可以更好地判别两者是否真具有“IS-A”的关系(注意,我们应该关注对象的行为上是不是“IS-A”的关系,即基类和子类是否具有行为上的一致性)。

    解决方案

    以下两种情况时,可能会出现违反LSP的错误:

  • 子类的方法相比于父类呈现退化状态而非进化;

  • 子类抛出类父类没有的异常。

父类和子类如果违反了LSP,那么说明他们不具有行为上的一致性,即在行为上不具有“IS-A”关系,在这种时候,可以考虑将他们变成兄弟。

ISP接口隔离原则

Interface-Segregation Principle,接口隔离原则。
不应迫使客户程序依赖于它们不需要的接口。即,客户程序依赖的类中不应该含有其不需要的方法,从而降低系统的复杂度,减少类之间的耦合。

接口隔离的好处

降低耦合,提高复用性。

实现方法

将大的接口依据业务进行拆分,同时保持小的接口之间的隔离性。

DIP依赖倒置原则

依赖倒置的内容:

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

依赖倒置原则其中的『倒置』强调的就是高层模块与低层模块间的关系:高层模块作为需求方提出需求(提出接口),低层模块去实现高层模块提出的需求(接口)。
这是因为:

  • 高层模块不应知道低层模块的细节;
  • 若是由低层模块制定接口,很可能不由自主地将实现细节曝露在接口中,这是我们不希望看到的。

    实现方案

    当两个模块之间存在依赖时,高层模块将依赖的部分抽象出接口,低层模块去实现接口的细节。

    小结

    SRP、OCP、LSP三个原则,关注的是如何设计一个类或者如何设计父类及子类。
    ISP和DIP关注的是实体间出现依赖的时候,如何设计接口来优化依赖。