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)
-
软件实体应该是:
对扩展开放
对修改封闭 -
这个原则是诸多面向对象编程原则中最抽象、最难理解的一个。
-
关键解决方案:抽象技术。 使用继承和组合来改变类的行为。
第一个栗子:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TtKvzJZh-1596860568311)(https://i.loli.net/2020/05/19/HMlbcEqzZvURIoa.png)]
第二个例子:以书店销售书籍为例
书籍接口
public interface IBook{
public String getName();
public String getPrice();
public String getAuthor();
}
小说类
public class NovelBook implements IBook{
private String name;
private int price;
private String author;
public NovelBook(String name,int price,String author){
this.name = name;
this.price = price;
this.author = author;
}
public String getAutor(){
return this.author;
}
public String getName(){
return this.name;
}
public int getPrice(){
return this.price;
}
}
客户端
public class Client{
public static void main(Strings[] args){
IBook novel = new NovelBook("深入理解计算机系统",100,"CMU");
System.out.println("书籍名字:"+novel.getName()+"书籍作者:"+novel.getAuthor()+"书籍价格:"+novel.getPrice());
}
}
接下来,到了世界读书日,书店打折,需要做出改变。有一下几种修改方法:
-
修改接口
IBook里添加方法getOffPrice()(接口应该是稳定且可靠,不应该经常发生改变) -
修改实现类
NovelBook里将getPrice()方法修改为打折价格(我们如果getPrice()方法中只需要读取书籍的打折前的价格呢?)
NovelBook里再增加getOffPrice()方法(DIP原则(后文会讲到)要求尽量用接口定义变量,只在具体实现类里新增方法,用接口定义的变量无法使用这个方法) -
实现拓展类
增加一个OffNovelBook类继承NovelBook,重写getPrice()方法,修改为打折价格
public class OffNovelBook extends NovelBook{
public OffNovelBook(String name,int price,String author){
super(name,price,author);
}
//覆写价格方法
public int getPrice(){
return this.price * 0.9;
}
}
}
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中,抽象就是接口和抽象类,细节就是实现类,翻译过来就是:
-
模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
-
接口或抽象类不依赖实现类
-
实现类依赖接口或抽象类
一句话概括就是——“面向接口编程”
-
前人经验:
-
每个类尽量都有接口或者抽象类,或者抽象类和接口两都具备
-
变量的表面类型尽量是接口或者抽象类
-
任何类都不应该从具体类派生
-
尽量不要覆写基类的方法
-
如果基类是一个抽象类,而这个方法已经实现了,子类尽量不要覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性会有一定的影响。
-
结合里氏替换原则使用
-