单一职责原则 SRP
单一职责原则的英文是Single Responsibility Principle,缩写为SRP。
英文描述: A class or module should have a single reponsibility。
中文描述: 一个类或者模块只负责完成一个职责(或者功能)。
如何理解单一职责原则?
- 一个类只负责完成一个职责或者功能。
- 不要设计大而全的类,要设计粒度小、功能单一的类。
- 单一职责原则是为了实现代码高内聚、低耦合,提高代码的复用性、可读性、可维护性。
如何判断类的职责是否足够单一?
不同的应用场景、不同阶段的需求背景、不同的业务层面,对同一个类的职责是否单一,可能会有不同的判定结果。实际上,一些侧面的判断指标更具有指导意义和可执行性,比如,出现下面这些情况就有可能说明这类的设计不满足单一职责原则:
- 类中的代码行数、函数或者属性过多;
- 类依赖的其他类过多,或者依赖类的其他类过多;
- 私有方法过多;
- 比较难给类起一个合适的名字;
- 类中大量的方法都是集中操作类中的某几个属性。
类的职责是否设计得越单一越好?
- 单一职责原则通过避免设计大而全的类,避免将不相关的功能耦合在一起,来提高类的内聚性。
- 同时,类职责单一,类依赖的和被依赖的其他类也会变少,减少了代码的耦合性,以此来实现代码的高内聚、低耦合。
- 但是,如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性。
开闭原则
开闭原则的英文全称是Open Closed Principle,简写为OCP。
英文描述: software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification。
中文描述: 软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。
如何理解“对扩展开放、对修改关闭”?
添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。
关于上述定义有两点要注意:
- 第一点是,开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发。
- 第二点是,同样的代码改动,在粗代码粒度下,可能被认定为“修改”;在细代码粒度下,可能又被认定为“扩展”。
如何做到“对扩展开放、修改关闭”?
时刻具备扩展意识、抽象意识、封装意识。
在写代码的时候要多花点时间思考一下,这段代码未来可能有哪些需求变更,如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,在不改动代码整体结构、做到最小代码改动的情况下,将新的代码灵活地插入到扩展点上。
里氏替换原则
里式替换原则的英文翻译是:Liskov Substitution Principle,缩写为LSP。
英文描述: 原始描述为If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program。新描述为 Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it。
中文描述: 子类对象(object of subtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。
如何理解 里氏替换原则 ?
里式替换原则是用来指导,继承关系中子类该如何设计的一个原则。理解里式替换原则,最核心的就是理解“design by contract,按照协议来设计
”这几个字。父类定义了函数的“约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的“约定”。这里的约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。
举几个违反里式替换原则的例子来解释一下
- 1.子类违背父类声明要实现的功能
- 2.子类违背父类对输入、输出、异常的约定
- 3.子类违背父类注释中所罗列的任何特殊说明
替换原则跟多态的区别
虽然从定义描述和代码实现上来看,多态和里式替换有点类似,但它们关注的角度
是不一样的。
- 多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。它是一种代码实现的思路。
- 里式替换是一种设计原则,用来指导继承关系中子类该如何设计,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑及不破坏原有程序的正确性。
接口隔离原则
接口隔离原则的英文翻译是“ Interface Segregation Principle”,缩写为ISP。
英文描述: Clients should not be forced to depend upon interfaces that they do not use。
中文描述: 客户端不应该强迫依赖它不需要的接口。其中的“客户端”,可以理解为接口的调用者或者使用者。
如何理解“接口隔离原则”?
接口在软件开发中,既可以把它看作一组抽象的约定,也可以具体指系统与系统之间的API接口,还可以特指面向对象编程语言中的接口等。
理解接口隔离原则的关键,就是理解其中的“接口”二字。在这条原则中,我们可以把“接口”理解为下面三种东西:
- 一组API接口集合: 如果把“接口”理解为一组接口集合,可以是某个微服务的接口,也可以是某个类库的接口等。如果部分接口只被部分调用者使用,就需要将这部分接口隔离出来,单独给这部分调用者使用,而不强迫其他调用者也依赖这部分不会被用到的接口。
- 单个API接口或函数: 如果把“接口”理解为单个API接口或函数,部分调用者只需要函数中的部分功能,那就需要把函数拆分成粒度更细的多个函数,让调用者只依赖它需要的那个细粒度函数。
- OOP中的接口概念: 如果把“接口”理解为OOP中的接口,也可以理解为面向对象编程语言中的接口语法。那接口的设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数。
接口隔离原则与单一职责原则的区别
- 单一职责原则针对的是模块、类、接口的设计。
- 接口隔离原则相对于单一职责原则,一方面更侧重于接口的设计,另一方面它的思考角度也是不同的。
接口隔离原则提供了一种判断接口的职责是否单一的标准:通过调用者如何使用接口来间接地判定。如果调用者只使用部分接口或接口的部分功能,那接口的设计就不够职责单一。
依赖反转原则/依赖倒置原则
如何理解“控制反转”?
控制反转的英文翻译是Inversion Of Control,缩写为IOC。实际上,控制反转是一个比较笼统的设计思想,并不是一种具体的实现方法,一般用来指导框架层面的设计。这里所说的“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程通过框架来控制。流程的控制权从程序员“反转”给了框架。
如何理解“依赖注入”?
依赖注入的英文翻译是Dependency Injection,缩写为DI。依赖注入和控制反转恰恰相反,它是一种具体的编码技巧。我们不通过new的方式在类内部创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类来使用。
如何理解“依赖注入框架”?
我们通过依赖注入框架提供的扩展点,简单配置一下所有需要的类及其类与类之间依赖关系,就可以实现由框架来自动创建对象、管理对象的生命周期、依赖注入等原本需要程序员来做的事情。
如何理解“依赖反转原则”?
依赖倒置原则的英文翻译是"Dependence Inversion Principle", 缩写为 DIP.
英文描述: 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.
中文描述: 高层模块(high-level modules)不要依赖低层模块(low-level)。高层模块和低层模块应该通过抽象(abstractions)来互相依赖。除此之外,抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions)。
依赖反转原则也叫作依赖倒置原则。这条原则跟控制反转有点类似,主要用来指导框架层面的设计。高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。
高层模块和低层模块的划分方式
简单来说就是,在调用链上,调用者属于高层,被调用者属于低层。在平时的业务代码开发中,高层模块依赖底层模块是没有任何问题的。实际上,这条原则主要还是用来指导框架层面的设计,跟前面讲到的控制反转类似。
迪米特法则
什么是“高内聚、松耦合”?
- “高内聚、松耦合”是一个非常重要的设计思想,能够有效地提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。
- “高内聚、松耦合”是一个比较通用的设计思想,可以用来指导不同粒度代码的设计与开发,比如系统、模块、类,甚至是函数,也可以应用到不同的开发场景中,比如微服务、框架、组件、类库等。
- 在这个设计思想中,“高内聚”用来指导类本身的设计,“松耦合”用来指导类与类之间依赖关系的设计。不过,这两者并非完全独立不相干。高内聚有助于松耦合,松耦合又需要高内聚的支持。
什么是“高内聚”呢?
所谓高内聚,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一个类中。相近的功能往往会被同时修改,放到同一个类中,修改会比较集中,代码容易维护。(单一职责原则是实现代码高内聚非常有效的设计原则。
)
什么是“松耦合”呢?
所谓松耦合是说,在代码中,类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的代码改动不会或者很少导致依赖类的代码改动。(依赖注入、接口隔离、基于接口而非实现编程,以及迪米特法则,都是为了实现代码的松耦合。
)
“内聚”和“耦合”之间的关系
“高内聚”有助于“松耦合”,同理,“低内聚”也会导致“紧耦合”。
如何理解“迪米特法则”?
迪米特法则的英文翻译是"Law of Demeter", 缩写为 LOD. 有另外一个更加达意的名字,叫作最小知识原则,英文翻译为:The Least Knowledge Principle。
英文描述: 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.
中文描述: 每个模块只应该了解那些与它关系密切的模块的有限知识。或者说,每个模块只和自己的朋友“说话”,不和陌生人“说话”。
通俗理解: 不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口(也就是定义中的“有限知识”)。迪米特法则是希望减少类之间的耦合,让类越独立越好。每个类都应该少了解系统的其他部分。一旦发生变化,需要了解这一变化的类就会比较少。
“高内聚、松耦合”“单一职责原则”“接口隔离原则”“基于接口而非实现编程”“迪米特法则”,它们之间的区别和联系吗?
区别
- 高内聚、松耦合: 是一个重要的设计思想,能够有效地提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围.
- 单一职责原则: 提供的功能上要单一.
- 接口隔离原则: 与外部关系上只依赖需要的抽象.
- 基于接口而非实现编程: 是一条比较抽象、泛化的设计思想,为了提高代码的灵活性/扩展性/可维护性.
- 迪米特法则: 每个单元只该依赖与它关系密切的单元,最少知道,只与关系密切的单一交互.
联系
- 职责越单一越容易做到接口隔离,也越容易做到最少知道的迪米特法则.
- 基于抽象编程抽象的知识越顶层越脱离具体实现,相对知道的内容就越少,也容易实现迪米特法则.
- 接口隔离原则与迪米特法则都强调只依赖需要的部分,接口隔离原则是相对偏上层来说的,迪米特法则是相对偏具体实现来说的.
- 单一职责原则/接口隔离原则/基于接口而非实现编程/迪米特法则都以实现代码的"高内聚、松耦合"为目的,提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围,降低风险.
KISS原则和YAGNI原则
"KISS原则"的定义
KISS原则的英文描述有好几个版本,比如下面这几个。
- Keep It Simple and Stupid.
- Keep It Short and Simple.
- Keep It Simple and Straightforward.
不过,仔细看你就会发现,它们要表达的意思其实差不多,翻译成中文就是:尽量保持简单。
如何理解“KISS原则”?
KISS原则就是保持代码可读和可维护的重要手段。
代码足够简单,也就意味着很容易读懂,bug比较难隐藏。即便出现bug,修复起来也比较简单。
"KISS原则"怎么落地?
- 不要使用同事可能不懂的技术来实现代码。比如前面例子中的正则表达式,还有一些编程语言中过于高级的语法等。
- 不要重复造轮子,要善于使用已经有的工具类库。经验证明,自己去实现这些类库,出bug的概率会更高,维护的成本也比较高。
- 不要过度优化。不要过度使用一些奇技淫巧(比如,位运算代替算术运算、复杂的条件语句代替if-else、使用一些过于底层的函数等)来优化代码,牺牲代码的可读性。
"YAGNI原则"的定义
YAGNI原则的英文全称是:You Ain’t Gonna Need It。直译就是:你不会需要它。
如何理解“YAGNI原则”?
当用在软件开发中的时候,它的意思是:不要去设计当前用不到的功能;不要去编写当前用不到的代码。实际上,这条原则的核心思想就是:不要做过度设计。
KISS原则和YAGNI原则
YAGNI原则跟KISS原则并非一回事儿。
- KISS原则讲的是“如何做”的问题(尽量保持简单)
- YAGNI原则说的是“要不要做”的问题(当前不需要的就不要做)
DRY原则
如何理解“DRY原则”?
英文描述为:Don’t Repeat Yourself。
中文直译为:不要重复自己。
应用在编程中可以理解为:不要写重复的代码。
如何应用"DRY原则"?
三种代码重复的情况:实现逻辑重复、功能语义重复、代码执行重复。
- 实现逻辑重复,但功能语义不重复的代码,并不违反DRY原则。
- 实现逻辑不重复,但功能语义重复的代码,也算是违反DRY原则。
- 除此之外,代码执行重复也算是违反DRY原则。
网友评论