设计原则
前言
设计原则是指导代码设计的一些经验总结。设计原则这块儿的知识有一个特点,那就是这些原则听起来都比较抽象,定义描述都比较模糊,不同的人会有不同的解读。它们共同的目标都是高内聚低耦合,高内聚用来指导类的设计,低耦合用来指导类之间的依赖关系,高内聚有助于松耦合,松耦合需要高内聚的支持。
SRP 单一职责原则
原则定义:一个类或者模块只负责完成一个职责(或者功能)。类和模块可以有两种理解,一种理解是把模块看作比类更加抽象的概念,类也可以看作模块。 另一种理解是把模块看作比类更加粗粒度的代码块,模块中包含多个类,多个类组成一个模块。
单一职责原则通过避免设计大而全的类,避免将不相关的功能耦合在一起,来提高类的内聚性。同时,类职责单一,类依赖的和被依赖的其他类也会变少,减少了代码的耦合性,以此来实现代码的高内聚、低耦合。但是,如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性。
但大部分情况下,类里的方法是归为同一类功能,还是归为不相关的两类功能,并不是那么容易判定的。不同的应用场景、不同阶段的需求背景、不同的业务层面,对同一个类的职责是否单一,可能会有不同的判定结果。但是可以从一些侧面的指标来判断类的职责是否单一,出现下面这些情况就有可能说明这类的设计不满足单一职责原则:
- 类中的代码行数、函数或者属性过多。
- 类依赖的其他类过多,或被依赖的类过多。
- 私有方法过多。
- 类名比较难取。
- 类中大量的方法都是集中操作类中的某几个属性
OCP 开闭原则
原则定义:添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。
但是开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发。同样的代码改动,在粗代码粒度下,可能被认定为修改;在细代码粒度下,可能又被认定为扩展。比如,添加属性和方法相当于修改类,在类这个层面,这个代码改动可以被认定为修改;但这个代码改动并没有修改已有的属性和方法,在方法(及其属性)这一层面,它又可以被认定为扩展。
如果修改并没有破坏原有代码的运行,没有破坏原有的单元测试,那么这次修改就是一个合格的改动。
LSP 里式替换原则
原则定义:子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。最核心的就是理解“按照协议来设计”,父类定义了函数的“约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的“约定”。这里的约定包括,函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。
常见违背LSP代码特征:
- 子类违背父类声明要实现的功能,譬如:父类中提供的 SortOrdersByAmount() 订单排序函数,是按照金额从小到大来给订单排序的,而子类重写这个 SortOrdersByAmount() 订单排序函数之后,是按照创建日期来给订单排序的。那子类的设计就违背里式替换原则。
- 子类违背父类对输入、输出、异常的约定。譬如:在父类中,某个函数约定运行出错的时候返回 null;获取数据为空的时候返回空集合(empty collection)。而子类重载函数之后,实现变了,运行出错返回异常(exception),获取不到数据返回 null。那子类的设计就违背里式替换原则。
ISP 接口隔离原则
原则定义:客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。也就是要接口的职责更加单一。
不要建立庞大臃肿的接口,尽量细化接口。不要试图去建立一个很庞大的接口供所有依赖它的类去调用,依赖几个专用的接口要比依赖一个综合的接口更灵活。
接口隔离原则相对于单一职责原则,面更侧重于接口的设计,接口隔离原则提供了一种判断接口的职责是否单一的标准,通过调用者如何使用接口来间接地判定,如果调用者只使用部分接口或接口的部分功能,那接口的设计就不够职责单一。
DIP 依赖倒置原则
原则定义:高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。
所谓高层模块和低层模块的划分,简单来说就是,在调用链上,调用者属于高层,被调用者属于低层。在平时的业务代码开发中,高层模块依赖底层模块是没有任何问题的。实际上,这条原则主要还是用来指导框架层面的设计。譬如 IOC。
Kiss原则
直译”尽量保持简单。KISS 原则就是保持代码可读和可维护的重要手段。代码足够简单,也就意味着很容易读懂,bug 比较难隐藏。即便出现 bug,修复起来也比较简单。并不是代码数量越少就月简单,也并不越复杂的代码就违背了Kiss原则,本身就复杂的问题,用复杂的方法解决,并不违背 KISS 原则。同样的代码,在某个业务场景下满足 KISS 原则,换一个应用场景可能就不满足了。
开发的时候,一定不要过度设计,不要觉得简单的东西就没有技术含量。实际上,越是能用简单的方法解决复杂的问题,越能体现一个人的能力。
几个满足Kiss的方法:
- 不要使用同事可能不懂的技术来实现代码,还有一些编程语言中过于高级的语法等。
- 不要重复造轮子,要善于使用已经有的工具类库。经验证明,自己去实现这些类库,出 bug 的概率会更高,维护的成本也比较高。
- 不要过度优化。不要过度使用一些奇技淫巧(比如,位运算代替算术运算、复杂的条件语句代替 if-else、使用一些过于底层的函数等)来优化代码,牺牲代码的可读性。
- code review 的时候,同事对你的代码有很多疑问,那就说明你的代码有可能不够“简单”,需要优化。
YAGNI原则
直译“你不会需要它”,当用在软件开发中的时候,它的意思是不要去设计当前用不到的功能,不要去编写当前用不到的代码。实际上,这条原则的核心思想就是不要做过度设计。当然,这并不是说就不需要考虑代码的扩展性。
DRY原则
DRY(DRY: Don’t repeat yourself)原则就是不要写重复的代码。不是只代码重复,而是“知识”的重复,意思是指业务逻辑。例如由于沟通不足,两个程序员用两种不同的方法实现同样功能的校验。
典型的代码重复情况,实现逻辑重复、功能语义重复和代码执行重复。
实现逻辑重复,但功能语义不重复的代码,并不违反 DRY 原则,譬如如下代码。
1 |
|
实现逻辑不重复,但功能语义重复的代码,也算是违反 DRY 原则,譬如通过同的实现方式去实现一个一模一样的功能,除此之外,代码执行、注释或者文档。
不写重复的代码并不代表可复用。在一个项目代码中,可能不存在任何重复的代码,但也并不表示里面有可复用的代码,不重复和可复用完全是两个概念。所以,从这个角度来说,DRY 原则跟代码的可复用性讲的是两回事。
LOD迪米特法则
最小知识原则,每个模块应该只关心自己密切相关的模块的有限知识。不应该直接有依赖的类之间不要有依赖,有依赖关系的类应该尽量只依赖必要的接口。也就是低耦合,只有类处于弱耦合状态,类的复用率才会提高