说起来设计原则的第一篇是在6月份,之后7、8月因为工作比较忙基本都没怎么学习,9月份才又赶上来了,这段时间基于极客时间的《设计模式之美》重新学习了下经典设计原则,收获认知提升颇多。这里对这一阶段的经典设计原则的学习和重点进行一个小结。
话不多说,对这段时间学习的七篇设计原则进行总结
这其中涉及到不少知识点,总体总结在这里:

左侧的5 个设计原则组成的SOLID,它们分别是:SRP单一职责原则(the single responsibility principle )、OCP开闭原则(the open closed principle)、LSP里氏替换原则(the liskov substitution principle)、ISP接口隔离原则(the interface segregation principle)、DIP依赖反转原则(the dependency inversion principle)
接下来回顾一下这些设计原则学习过程中的重点
以下内容为我认为认知得到加深的内容:
SRP单一职责原则的定义是什么
一个类或者模块只负责完成一个职责(或者功能),也就是说,不要设计大而全的类,要设计粒度小、功能单一的类。换个角度来讲就是,一个类包含了两个或者两个以上业务不相干的功能,那我们就说它职责不够单一,应该将它拆分成多个功能更加单一、粒度更细的类
SRP原则解决什么问题
单一职责原则的目标是设计粒度小、功能单一的类,从类自身功能角度出发考虑。通过重构类的功能实现代码高内聚(功能相关高内聚)、促进代码低耦合(功能无关低耦合),提高代码的可复用性、可读性、可维护性
如何实现SRP原则
主观上依据当前场景合理拆分,开始设计要尽量避免过度设计,因为根本不知道业务未来会发展成什么样,能做的最好只是提前预留好扩展点,静候业务发展然后实时做出调整,可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构
客观上可以参照如下几个标准,下面这几条判断原则,比起很主观地去思考类是否职责单一,要更有指导意义、更具有可执行性:
有了主观重构意识再加上客观条件判断能帮助我们写出符合SRP的代码
以下内容为我认为认知得到加深的内容:
OCP开闭原则的定义是什么
软件实体(模块、类、方法等)应该对扩展开放、对修改关闭。详细表述就是,添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)
对于方法的扩展是对于类的修改,这样不满足OCP么
OCP规定代码一行都不能改么
写代码越OCP越好么
OCP原则解决什么问题
OCP的目标就是提高代码的可扩展性,23种设计模式大多数也是这个目标,所以说OCP非常重要
如何实现OCP原则
主观上要具备扩展意识、抽象意识、封装意识 ,具体的方法论则是:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态等)
以下内容为我认为认知得到加深的内容:
LSP里氏替换原则的定义是什么
子类对象(object of subtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。
LSP原则解决什么问题
用来指导继承关系中子类该如何设计 ,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑及不破坏原有程序的正确性,保证父子关系更加稳定健壮,通常用于指导框架版本向后兼容增强而不改变原有功能的设计。搭配【基于接口而非实现编程】使用效果最佳
如何实现LSP原则
子类在设计的时候,要遵守父类的行为约定(或者叫协议)。父类定义了函数的行为约定,那子类可以改变函数的内部实现逻辑,但不能改变函数原有的行为约定。这里的行为约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。实际上,定义中父类和子类之间的关系,也可以替换成接口和实现类之间的关系
以下内容为我认为认知得到加深的内容:
ISP接口隔离原则的定义是什么
客户端不应该被强迫依赖它不需要的接口。其中的客户端,可以理解为接口的调用者或者使用者,这里的接口不仅指Java里的接口类,实际上它有三种含义: 一组 API 接口或方法集合; 单个 API 接口或方法; OOP 中的接口概念
SRP和ISP都是降低代码依赖耦合,区别是什么
ISP原则解决什么问题
解决代码的无用依赖问题,指导接口的设计,让接口的功能更内聚单一,让调用方和接口直接的耦合度降低,提升接口的可复用性、可读性、可维护性
如何实现ISP原则
以下内容为我认为认知得到加深的内容:
DIP依赖反转原则的定义是什么
高层模块(high-level modules)不要依赖低层模块(low-level)。高层模块和低层模块应该通过抽象(abstractions)来互相依赖。除此之外,抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions),所谓高层模块和低层模块的划分,简单来说就是,在调用链上,调用者属于高层,被调用者属于低层
所谓反转并不是指反过来低层要依赖高层,而是二者都依赖抽象,这里的反转指的是原本高层依赖低层,在DIP下实际上是规范低层次模块的设计。低层次模块提供的接口要足够的抽象、通用,在设计时需要考虑高层次模块的使用种类和场景。明明是高层次模块要使用低层次模块,对低层次模块有依赖性。现在反而低层次模块需要根据高层次模块来设计,出现了「倒置」的显现
DIP原则解决什么问题
DIP用来指导框架设计,框架是高层,我们写的代码是低层,我们要按照通用的约定实现自己的底层代码,以满足高层的调用需求。例如:
如何实现DIP原则
如果我们编写框架,就得让框架依赖的抽象规范明确,基于抽象而非实现编程;如果我们编写实现代码,要想让框架或者容器能调用我们的实现方法实现功能,我们就得依赖满足高层运行功能抽象规范去写自己的实现,也就是按照约定编程。
以下内容为我认为认知得到加深的内容:
KISS保持简单原则的定义是什么
KISS原则:Keep It Simple and Stupid。翻译成中文就是:尽量保持简单,这里的简单并不是代码行数越少就越简单,还要考虑逻辑复杂度、实现难度、代码的可读性,还有一点就是本身就复杂的问题,用复杂的方法解决,并不违背 KISS 原则
KISS原则解决什么问题
KISS 原则是保持代码可读和可维护的重要手段。代码足够简单,也就意味着很容易读懂,bug 比较难隐藏。即便出现 bug,修复起来也比较简单。
如何实现KISS原则
KISS原则其实本来就比较主观,不过还是有一些验证方式的:
主观验证的方式就是代码CR,让大家看看是否满足。
以下内容为我认为认知得到加深的内容:
YAGNI勿过度设计原则的定义是什么
不要去设计当前用不到的功能;不要去编写当前用不到的代码。实际上,这条原则的核心思想就是:不要做过度设计,当然这并不意味着我们不要预留代码扩展点
YAGNI原则解决什么问题
防止项目中代码冗余,防止降低代码的可读性和可维护性
如何实现YAGNI原则
感觉未来要实现的地方,预留扩展点,先不要实现。
以下内容为我认为认知得到加深的内容:
DRY勿重复编码原则的定义是什么
就是字面意思:不要写重复的代码。但这里的重复有不同的含义,包括:实现逻辑重复(代码写重复了)、功能语义重复(方法的用处相同)、代码逻辑重复(方法内代码块重复执行)
DRY原则可以提升代码的复用性么?
如何提高代码复用性
DRY原则解决什么问题
减少项目中的重复功能代码,提高代码的可读性和可维护性
如何实现DRY原则
实现逻辑重复,但功能语义不重复的代码,并不违反 DRY 原则,可以通过更细粒度的SRP改造来去除重复代码。实现逻辑不重复,但功能语义重复的代码,也算是违反 DRY 原则。除此之外,代码执行重复也算是违反 DRY 原则,后边这两种情况需要通过重构去除重复功能方法、逻辑代码块以及重构来满足DRY
以下内容为我认为认知得到加深的内容:
LOD迪米特最小知道法则的定义是什么
每个模块(unit)只应该了解那些与它关系密切的模块的有限知识(knowledge)也即每个模块只和自己的朋友说话,不和陌生人说话,不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口(也就是定义中的“有限知识”)
LOD迪米特法则解决什么问题
让代码尽量高内聚,低耦合,重点侧重低耦合,设计类间关系时要多加小心,防止过度依赖。目的也是提高代码的可读性和可维护性
如何实现LOD法则
这里对所有涉及到的原则和法则做一个横向总结,这里的接口指OOP中的接口或抽象类。
| 设计原则 | 应用范围 | 看待视角 | 解决什么问题 |
|---|---|---|---|
| SRP单一职责原则 | 模块、类(接口)、方法 | 实体自身视角 | 提高代码的内聚性,可复用性、可读性、可维护性 |
| OCP开闭原则 | 模块、类(接口)、方法 | 实体自身视角 | 提高代码的可扩展 |
| LSP里氏替换原则 | 类(接口) | 父子间关系视角 | 指导子类设计、继承设计 |
| ISP接口隔离原则 | 接口 | 实体间关系视角 | 降低调用者或使用者依赖,提升接口的可复用性、可读性、可维护性 |
| DIP依赖反转原则 | 模块 | 框架设计视角 | 指导框架设计 |
| KISS保持简单原则 | 方法 | 全局视角 | 提高代码的简洁性、可读性、可维护性 |
| YAGNI勿过度设计原则 | 类(接口)、方法 | 全局视角 | 不过度设计只预留扩展防止降低代码的可读性和可维护性 |
| DRY勿重复编码原则 | 类(接口)、方法 | 全局视角 | 去除重复代码 防止降低代码的可读性和可维护性 |
| LOD迪米特法则 | 类(接口) | 实体间关系视角 | 防止类间过度依赖,降低代码的耦合性,提高代码的可读性和可维护性 |
其实无论是SOLID、还是KISS、YAGNI、DRY以及LOD,都是服务于写出高质量代码。简单而言就是写出:可维护性、可读性、可扩展性、灵活性、简洁性(简单、复杂)、可复用性、可测试性强的高质量代码。可读性、可维护性的一个基本设计就是写出高内聚、低耦合的代码,其中SRP服务于高内聚,ISP和LOD服务于低耦合。毋庸置疑OCP服务于代码的扩展性,LSP和DIP场景就比较具体了:LSP用于指导父子关系设计,DIP用于指导框架设计。而KISS、YAGNI以及DRY都是从代码的整体视角去告诉我们写代码要尽量简单明了,不要写现在没用或重复的代码,这样代码的可读性和可维护性才高。