SOLID 原则提供了五条指导思想,如果我们遵从它们的话, 将可以显著的提升我们软件可维护性。
- SRP 单一责任原则(The Single Responsibility Principle)
- OCP 开放封闭原则(The Open Closed Principle)
- LSP 里氏替换原则(The Liskov Substitution Principle)
- ISP 接口分离原则(The Interface Segregation cPrinciple)
- DIP 依赖转置原则(The Dependency Inversion Principle)
SRP 单一责任原则(The Single Responsibility Principle)
单一职责原则(SRP)声明:“引起类变化的因素永远不要多余一个”。这意味着你需要设计你的类,使得每个类都只有一个目的。这并不意味着每个类应该只有一个方法,而是说类中所有的方法都要与该类的主要功能相关。那些有多个职责的类,应该被分成新的类。
—- One class, one responsibility
SRP是最简单的原则,却是最难做好的原则。
来个反例:
OCP 开放封闭原则(The Open Closed Principle)
软件实体应该是:
对扩展开放
对修改封闭这个原则是诸多面向对象编程原则中最抽象、最难理解的一个。
关键解决方案:抽象技术。 使用继承和组合来改变类的行为。
第一个栗子:
第二个例子:以书店销售书籍为例
书籍接口
1 | public interface IBook{ |
小说类
1 | public class NovelBook implements IBook{ |
客户端
1 | public class Client{ |
接下来,到了世界读书日,书店打折,需要做出改变。有一下几种修改方法:
修改接口
IBook里添加方法getOffPrice()(接口应该是稳定且可靠,不应该经常发生改变)修改实现类
NovelBook里将getPrice()方法修改为打折价格(我们如果getPrice()方法中只需要读取书籍的打折前的价格呢?)NovelBook里再增加getOffPrice()方法(DIP原则(后文会讲到)要求尽量用接口定义变量,只在具体实现类里新增方法,用接口定义的变量无法使用这个方法)实现拓展类
增加一个OffNovelBook类继承NovelBook,重写getPrice()方法,修改为打折价格
1 | public class OffNovelBook extends NovelBook{ |
LSP 里氏替换原则(The Liskov Substitution Principle)
两种定义:
- 如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换为o2,程序P的行为没有发生变化,那么类型S是类型T的子类型。
- 所有引用基类的地方必须透明的使用其子类的对象。(只要父类能出现的地方子类也可以出现,而且替换为子类不会产生任何错误或异常,但是反过来就不行,有子类出现的地方,父类未必就能适应。)
里氏替换原则的规范
LSP为良好的继承定义了规范:
- 子类必须完全实现父类的方法
- 子类可以有自己的个性
- 覆盖或实现父类的方法时输入参数可以被放大(反协变)(java会将此认作方法重载)
- 覆盖或实现父类的方法时输出结果可以被缩小(协变)
ISP 接口分离原则(The Interface Segregation cPrinciple)
两种定义:
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 interface。类间的依赖关系应该建立在最小的接口上。
理解:
- 建立单一接口 ,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
- 依赖几个专用的接口要比依赖一个综合的接口:系统的灵活性更高,可维护性更好
区别一下单一职责原则和接口分离原则:
- 单一职责原则注重的是职责;接口隔离原则注重对接口依赖的隔离。
- 单一职责原则主要是约束类;接口隔离原则主要约束接口
DIP 依赖转置原则(The Dependency Inversion Principle)
定义:
- 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 abstracts.细节应该依赖抽象
在java中,抽象就是接口和抽象类,细节就是实现类,翻译过来就是:
模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
接口或抽象类不依赖实现类
实现类依赖接口或抽象类
一句话概括就是——“面向接口编程”
前人经验:
每个类尽量都有接口或者抽象类,或者抽象类和接口两都具备
变量的表面类型尽量是接口或者抽象类
任何类都不应该从具体类派生
尽量不要覆写基类的方法
如果基类是一个抽象类,而这个方法已经实现了,子类尽量不要覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性会有一定的影响。
结合里氏替换原则使用