设计原则

1. 单一职责原则

一个类只负责完成一个功能,不要设计太多其他功能,过多就应该拆分。单一职责是为了实现高内聚、低耦合,提高代码的复用性、可读性、扩展性。

判断一个类的单一职责:

  • 代码行数过多
  • 字段和方法过多
  • 依赖过多
  • 比较难给类起一个名字

当然,不是越细分就越好,太细了就无法内聚了,都是要跟具体==业务==逻辑相结合。

2. 开闭原则

对扩展开放,对修改关闭。

应用开闭原则以适应业务中的变化,增加或删除功能,测试都要比较方便。

扩展手段:

  1. 分离变化与不变的,使用handler处理
  2. 有抽象、封装、接口意识
  3. 多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态等)

我们没必要纠结某个代码改动是“修改”还是“扩展”,更没必要太纠结它是否违反“开闭原则”。我们回到这条原则的设计初衷:只要它没有破坏原有的代码的正常运行,没有破坏原有的单元测试,我们就可以说,这是一个合格的代码改动。

灵活的应用开闭原则需要对业务的理解,经验的累计。

3. 里氏替换

子类可以在任何地方替换父类,而不影响原有程序的逻辑行为。

里氏替换的好处还是为了使得代码有更好的扩展性和维护性,父类和子类有一样的行为,也方便测试。

里氏替换最核心的就是design by contract按照协议来设计这几个字。父类定义了函数的“约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的“约定”。这里的约定包括:

  • 函数声明要实现的功能;
  • 对输入、输出、异常的约定;
  • 甚至包括注释中所罗列的任何特殊说明。

里氏替换和多态还是不一样的,多态是子类可以替换父类,更多情况是在形参上父类接受子类,就可以了。而里氏替换还需要满足约定,替换后,程序逻辑、功能、行为、表达含义还是要跟原有的一样。

4. 接口隔离

客户端不应该强迫依赖它不需要的接口。

接口可以是一组协议、函数、OOP中实际的接口。对调用者而言,只需要使用其中部分功能,那么这个接口就设计的不好,应该拆分。拆分后,形成两个或者多个接口,调用者只需使用它需要的那一个或多个接口就行,这样看起来接口之间就隔离开了。

相对于单一职责而言,接口隔离原则更注重于接口的设计;单一职责原则关注于模块、类、接口,更广度一些。

5.依赖反转

依赖反转原则也叫作依赖倒置原则。主要用来指导框架层面的设计。高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,它定义规范来链接两个模块。

控制反转:是一个比较笼统的设计思想,并不是一种具体的实现方法,一般用来指导框架层面的设计。这里所说的“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程通过框架来控制。流程的控制权从程序员“反转”给了框架。控制反转的实现可以通过依赖注入或者模板设计等方式来实现。

依赖注入:依赖注入和控制反转恰恰相反,它是一种具体的编码技巧。我们不通过 new 的方式在类内部创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类来使用。

6.KISS原则

Keep It Simple and Stupid. 尽量保持简单。

KISS 原则是保持代码可读和可维护的重要手段。代码行数越少并不代表代码越简单,我们还要考虑逻辑复杂度、实现难度、代码的可读性等。

进行code review时,判断KISS的几条指导原则:

  • 不要使用同事可能不懂的技术来实现代码;
  • 不要重复造轮子,要善于使用已经有的工具类库;
  • 不要过度优化。

7.DRY原则

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

三种代码重复的情况:实现逻辑重复、功能语义重复、代码执行重复。

提高代码可复用性的一些方法:

  • 减少代码耦合
  • 满足单一职责原则
  • 模块化业务与非业务逻辑分离
  • 通用代码下沉
  • 继承、多态、抽象、封装
  • 应用模板等设计模式

记住,不要过度优化。在第一次编写代码,未来需求不明确,就不要想着如何编码重复,先把当前功能做完,待以后有新的需求时,再重构。

8.迪米特法则

不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。

迪米特法则是希望减少类之间的耦合,让类越独立越好。每个类都应该少了解系统的其他部分。

“高内聚、松耦合”: 所谓高内聚,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一类中。所谓松耦合指的是,在代码中,类与类之间的依赖关系简单清晰,不要有过强的依赖关系。

大多数思想的目的都是实现高内聚低耦合,但是出发的角度不一样,单一职责是从自身提供的功能出发,迪米特法则是从依赖关系出发,针对接口而非实现编程是使用者的角度,殊途同归。