1 SOILD

1.1 单一职能原则(Single Responsibility Principle, SRP)

1.1.1 概念

A class or module should have a single reponsibility 一个类或者模块只负责完成一个职责(或者功能)

即一个类只负责完成一个职责或者功能。不要设计大而全的类,要设计粒度小、功能单一的 类。单一职责原则是为了实现代码高内聚、低耦合,提高代码的复用性、可读性、可维护性。

1.1.2 如何判断类的职责是否足够单一

不同的应用场景、不同阶段的需求背景、不同的业务层面,对同一个类的职责是否单一,可能会有不同的判定结果。实际上,一些侧面的判断指标更具有指导意义和可执行性,比如

  • 类中的代码行数、函数或者属性过多;
  • 类依赖的其他类过多,或者依赖类的其他类过多;
  • 私有方法过多;
  • 比较难给类起一个合适的名字;
  • 类中大量的方法都是集中操作类中的某几个属性。

    1.1.3 类的职责是否设计得越单一越好

    单一职责原则通过避免设计大而全的类,避免将不相关的功能耦合在一起,来提高类的内聚性。同时,类职责单一,类依赖的和被依赖的其他类也会变少,减少了代码的耦合性,以此来实现代码的高内聚、低耦合。但是,如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性。

1.2 开闭原则(Open Close Principle,OCP)

1.2.1 概念

software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification。 软件实体(模块、类、方法等) 应该“对扩展开放、对修改关闭”。

即软件实体应尽量在不修改原有代码的情况下进行扩展 开闭原则即对拓展开放,对修改关闭,要想在功能新增的时候不修改原先的代码。

1.2.2 如何做到“对扩展开放、修改关闭”

我们要时刻具备扩展意识、抽象意识、封装意识。在写代码的时候,我们要多花点时间思考 一下,这段代码未来可能有哪些需求变更,如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,在不改动代码整体结构、做到最小代码改动的情况下,将新的代码灵活地插入到扩展点上。
很多设计原则、设计思想、设计模式,都是以提高代码的扩展性为最终目的的。特别是 23 种经典设计模式,大部分都是为了解决代码的扩展性问题而总结出来的,都是以开闭原则为指导原则的。最常用来提高代码扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态)。

1.2.3 修改示例

警告:https://gitee.com/baozikun/design-pattern/tree/master/src/main/java/com/baozikun/designpattern/ch16

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

1.3.1 概念

Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it。 所有引用基类(父类)的地方必须能透明地使用其子类的对象 。

1.3.2 理解

里式替换原则是用来指导,继承关系中子类该如何设计的一个原则。理解里式替换原则,最核心的就是理解“design by contract,按照协议来设计”这几个字。父类定义了函数 的“约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的“约定”。这里的约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。
虽然从定义描述和代码实现上来看,多态和里式替换有点类似,但它们关注的角度是不一样的。多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。它是一种代码实现的思路。而里式替换是一种设计原则,用来指导继承关系中子类该如何设计,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑及不破坏原有程序的正确性。

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

1.4.1 概念

Clients should not be forced to depend upon interfaces that they do not use。 接口的调用者不应该强迫依赖 它不需要的接口。

1.4.2 理解

理解“接口隔离原则”的重点是理解其中的“接口”二字。这里有三种不同的理解。

  • 如果把“接口”理解为一组接口集合,可以是某个微服务的接口,也可以是某个类库的接口等。如果部分接口只被部分调用者使用,我们就需要将这部分接口隔离出来,单独给这部分调用者使用,而不强迫其他调用者也依赖这部分不会被用到的接口。
  • 如果把“接口”理解为单个 API 接口或函数,部分调用者只需要函数中的部分功能,那就需要把函数拆分成粒度更细的多个函数,让调用者只依赖它需要的那个细粒度函数。
  • 如果把“接口”理解为 OOP 中的接口,也可以理解为面向对象编程语言中的接口语法。那 接口的设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数。

    1.4.3 接口隔离原则与单一职责原则的区别

    单一职责原则针对的是模块、类、接口的设计。接口隔离原则相对于单一职责原则,一方面 更侧重于接口的设计,另一方面它的思考角度也是不同的。接口隔离原则提供了一种判断接口的职责是否单一的标准:通过调用者如何使用接口来间接地判定。如果调用者只使用部分接口或接口的部分功能,那接口的设计就不够职责单一。

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

1.5.1 概念

High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions. 高层模块不要依赖低层模。高层模块和低层模块应该通过抽象来互相依赖。除此之外,抽象不要依赖具体实现细节,具体实现细节依赖抽象。

抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程 依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。在实现依赖倒转原则时,我们需要针对抽象层编程,而将具体类的对象通过依赖注入(Dependency Injection, DI)的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。

2 KISS&YAGNI

Keep It Short and Simple 尽量保持简单

KISS 原则是保持代码可读和可维护的重要手段。
KISS 原则中的“简单”并不是以代码行数来考量的。代码行数越少并不代表代码越简单,我们还要考虑逻辑复杂度、实现难度、代码的可读性等。
本身就复杂的问题,用复杂的方法解决,并不违背 KISS 原则。

You Ain’t Gonna Need It 你不会需要它

不要做过度设计。KISS 原则讲的是“如何做”的问题(尽量保持简单),而 YAGNI 原则说的是“要不要做”的问题(当前不需要的就不要做)。

4 DRY

Don’t Repeat Yourself。 不要写重复的代码。

实现逻辑重复,但功能语义不重复的代码,并不违反 DRY 原则。
实现逻辑不重复,但功能语义重复的代码,也算是违反 DRY 原则。
代码执行重复,也算是违反 DRY 原则。

5 LOD

Each unit should have only limited knowledge about other units: only units “closely” related to the current unit. Or: Each unit should only talk to its friends; Don’t talk to strangers. 每个模块只应该了解那些与它关系密切的模块的有限知识)。或者说,每个模块只和自己 的朋友“说话”,不和陌生人“说话”。

即不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的 接口(也就是定义中的“有限知识”)。
一个软件实体应当尽可能少地与其他实体发生相互作用。类与类之间的耦合度应尽量的低,这样如果类发生变化,影响才会最小。不要和陌生人说话,只和你的直接朋友通信,直接朋友包含如下:

  • 当前对象本身(this)
  • 作为参数的对象
  • 成员变量
  • 集合成员变量中的元素
  • 创建的对象

    6 将设计原则运用到项目中

    6.1 系统设计

  • 技术人也要有一些产品思, 对于产品设计、需求分析,我们要学会“借鉴” 。

  • 可以借鉴面向对象设计的步骤,来做系统设计 。 面向对象设计聚焦在代码层面(主要是针对类),那系统设计就是聚焦在架构层面(主要是 针对模块) 。
  • 画产品线框图、聚焦简单应用场景、设计实现最小原型、画系统设计图等。