# SOLID设计原则
solid 为面向对象设计原则中的一种
程序设计领域, SOLID (单⼀功能、开闭原则、⾥⽒替换、接⼝隔离以及依赖反转)是由罗伯特·C·⻢丁在21世纪早期 引⼊的记忆术⾸字⺟缩略字,指代了⾯向对象编程和⾯向对象设计的五个基本原则。当这些原则被⼀起应⽤时,它们使得⼀个程序员开发⼀个容易进⾏软件维护和扩展的系统变得更加可能SOLID被典型的应⽤在测试驱动开发上,并且是敏捷开发以及⾃适应软件开发的基本原则的重要组成部分
主要分为:
- [S] Single Responsibility Principle (单⼀职责原则)
- [O] Open Close Principle (开闭原则)
- [L] Liskov Substitution Principle(⾥⽒替换原则)
- [I] Interface Segregation Principle(接⼝隔离原则)
- [D] Dependency Inversion Principle (依赖倒置/反转原则)
# 一、单一职责
【一个类应该只有一个发生变化的原因】
There should never be more than one reason for a class to change.
# 单一职责原则适用于类、接口、方法
单一职责原则简称 SRP ,顾名思义,就是一个类只负责一个职责。那这个原则有什么用呢,它让类的职责更单一。这样的话,每个类只需要负责自己的那部分,类的复杂度就会降低。如果职责划分得很清楚,那么代码维护起来也更加容易。试想如果所有的功能都放在了一个类中,那么这个类就会变得非常臃肿,而且一旦出现bug,要在所有代码中去寻找;更改某一个地方,可能要改变整个代码的结构,想想都非常可怕。当然一般时候,没有人会去这么写的。
当然,这个原则不仅仅适用于类,对于接口和方法也适用,即一个接口/方法,只负责一件事,这样的话,接口就会变得简单,方法中的代码也会更少,易读,便于维护。
事实上,由于一些其他的因素影响,类的单一职责在项目中是很难保证的。通常,接口和方法的单一职责更容易实现。
# 单一职责原则的好处
- 代码的粒度降低了,类的复杂度降低了。
- 可读性提高了,每个类的职责都很明确,可读性自然更好。
- 可维护性提高了,可读性提高了,一旦出现 bug ,自然更容易找到他问题所在。
- 改动代码所消耗的资源降低了,更改的风险也降低了。
# 二、开闭原则
一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭
Software entities like classes, modules and functions should be open for extension but closed for modification
# 变化带来的问题
在软件的生命周期内,因为变化,升级和维护等原因需要对软件原有代码进行修改,可能会给旧代码引入错误,也有可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
# 用开闭原则改善因变化带来的问题
当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现。
开闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定灵活的系统,开闭原则只定义了对修改关闭,对扩展开放。其实只要遵循SOLID中的另外5个原则,设计出来的软件就是符合开闭原则的。
# 用抽象构建架构,用实现扩展细节
用抽象构建架构,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保证架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了,当然前提是抽象要合理,要对需求的变更有前瞻性和预见性。
# 三、里氏替换原则
【所有引用基类的地方必须能透明地使用其子类的对象】
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
# 里氏替换原则弥补继承的缺陷
里氏替换原则的意思是,所有基类在的地方,都可以换成子类,程序还可以正常运行。这个原则是与面向对象语言的继承特性密切相关的。
在学习java类的继承时,我们知道继承有一些优点:
- 子类拥有父类的所有方法和属性,从而可以减少创建类的工作量。
- 提高了代码的重用性。
- 提高了代码的扩展性,子类不但拥有了父类的所有功能,还可以添加自己的功能。
但有优点也同样存在缺点:
- 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法。
- 降低了代码的灵活性。因为继承时,父类会对子类有一种约束。
- 增强了耦合性。当需要对父类的代码进行修改时,必须考虑到对子类产生的影响。
如何扬长避短呢?方法是引入里氏替换原则。
# 里氏替换原则对继承进行了规则上的约束
里氏替换原则对继承进行了规则上的约束,这种约束主要体现在四个方面:
- 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。
- 子类中可以增加自己特有的方法。
- 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比- 父类方法的输入参数更宽松。(即只能重载不能重写)
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
# 四、接口隔离原则
1、客户端不应该依赖它不需要的接口。 2、类间的依赖关系应该建立在最小的接口上。
Clients should not be forced to depend upon interfaces that they don`t use. The dependency of one class to another one should depend on the smallest possible.
以上两个定义的含义是:要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
注:该原则中的接口,是一个泛泛而言的接口,不仅仅指Java中的接口,还包括其中的抽象类。
# 接口隔离原则和单一职责的区别
接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的:
- 单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
- 单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。
# 接口隔离原则的优点
接口隔离原则是为了约束接口、降低类对接口的依赖性,遵循接口隔离原则有以下 5 个优点。
- 将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
- 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。
- 如果接口的粒度大小定义合理,能够保证系统的稳定性;但是,如果定义过小,则会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险。
- 使用多个专门的接口还能够体现对象的层次,因为可以通过接口的继承,实现对总接口的定义。
- 能减少项目工程中的代码冗余。过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫设计冗余的代码。
# 接口隔离原则的实现方法
在具体应用接口隔离原则时,应该根据以下几个规则来衡量。
- 根据接口隔离原则拆分接口时,首先必须满足单一职责原则。
- 接口尽量小,但是要有限度。一个接口只服务于一个子模块或业务逻辑。
- 为依赖接口的类定制服务。只提供调用者需要的方法,屏蔽不需要的方法。
- 了解环境,拒绝盲从。每个项目或产品都有选定的环境因素,环境不同,接口拆分的标准就不同深入了解业务逻辑。
- 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情
# 五、依赖倒置原则
# 依赖倒置原则定义
1、上层模块不应该依赖底层模块,它们都应该依赖于抽象。 2、抽象不应该依赖于细节,细节应该依赖于抽象。
High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.
首先,这个原则听起来很像是“针对接口编程,不针对现实编程”,不是吗?的确很相似,然而这里更强调“抽象”。
参考: