SOLID Principles是面向对象设计中的五个重要原则,这个SOLID就代表 (当然不是固体)
SRP 单一职责原则
OCP 开闭原则
LSP Liskov替换原则
ISP 接口隔离原则
DIP 依赖倒置原则
单一职责原则(Single Responsibility)
一个类只应该有一个职责,也就是说他的变更只能由一个原因。
所以这个原则怎么帮助我们呢?
1.测试——只有一个职责的类就无需过多的测试用例
2.更低的耦合——一个类的功能越少,依赖程度越低
3.更易管理——更小的类相比于庞大的更容易与管理
举个例子:
这是一个Book类
public class Book {
private String name;
private String author;
private String text;
//省略构造方法,getter,setter
}
我们再添加一些方法
public class Book {
private String name;
private String author;
private String text;
//省略构造方法,getter,setter
// 和属性有直接关联的方法
public String replaceWordInText(String word){
return text.replaceAll(word, text);
}
public boolean isWordInText(String word){
return text.contains(word);
}
}
再添加一个输出方法
public class Book {
//...
void printTextToConsole(){
// 输出到控制台的代码
}
}
这时候,就违反了我们的单一职责原则,我们应该建立一个专注于输出的类
public class BookPrinter {
//输出方法
void printTextToConsole(String text){
//输出到控制台的代码 }
void printTextToAnotherMedium(String text){
// 输出代码到其他地方的代码
}
}
这样,我们不仅遵循了单一职责,而且获得一个可以把内容输出到任意媒介的类。
开放封闭原则(open-close principle)
顾名思义,对功能扩展开放,对内部修改封闭。这样做我们就不对已有代码修改,不会造成潜在的新漏洞。当然,在我们修复bug时要修改代码。
举个例子:
假设我们有一个很厉害的吉他
public class Guitar {
private String make;
private String model;
private int volume;
//构造方法,getter,setter
}
但是过了几个月,我们觉得他有点无聊,有些单调,就想给他加一个火焰贴纸,让他更酷炫一点,但是谁知道这样会不会带来bug呢?这时候,就要遵循开闭原则。
public class SuperCoolGuitarWithFlames extends Guitar {
private String flameColor;
//构造方法,getter,setter
}
通过继承Guiltar类,我们可以确定已有的代码不会受影响。
Liskov替换原则(LisKov Substitution)
这个原则是SOLID里面最复杂的一个,简单的来说,如果类A是类B的一个子类,那么我们就应该在不干扰程序行为的情况下用类A替换类B。
让我们直接看代码:
我们定义一个汽车接口
public interface Car {
void turnOnEngine();
void accelerate();
}
让一辆车实现这个接口
public class MotorCar implements Car {
private Engine engine;
//构造方法,getter,setter
public void turnOnEngine() {
//启动引擎
engine.on();
}
public void accelerate() {
//前进
engine.powerOn(1000);
}
}
顺便一说,我们还有新能源汽车。
public class ElectricCar implements Car {
public void turnOnEngine() {
throw new AssertionError("我不需要引擎!");
}
public void accelerate() {
//加速很快啊啊啊
}
}
很好,这个例子完美的违反了我们的Liksov替换原则,所以我们应该做的是,在定义接口的时候,把电动车不需要引擎的情况也考虑进去。
接口隔离原则(Interface Segregation Principle)
简而言之,就是比较大的接口应该被拆分成更小的。这样就能确保要实现接口的类只需要专注于他们需要用的方法。
那么,假设我们是动物管理员,被分配了熊的领地 ; ( ,我们要清理熊,喂它,抚摸它
public interface BearKeeper {
void washTheBear();
void feedTheBear();
void petTheBear();
}
作为一名敬业的动物管理员,我们并不是很热衷于抚摸熊,这样太危险了。但是我们没有选择的余地,只能实现全部方法。
public interface BearCleaner {
void washTheBear();
}
public interface BearFeeder {
void feedTheBear();
}
public interface BearPetter {
void petTheBear();
}
感谢接口隔离原则,我们把这个接口拆分开来,这样就可以只实现我们想做的事情。
public class CrazyPerson implements BearPetter {
public void petTheBear() {
//祝你好运!!!
}
}
最后,这件危险的工作留给别人做吧。顺便一提,在第一个例子(单一职责)中,我们可以把printer接口拆分成两个功能的接口,这也是接口隔离原则的应用。
依赖倒置原则 (Dependency Inversion Principle)
依赖倒置原则就是把我们的模块解耦,不再是高等级模块依赖低等级模块,它们都将依赖抽象。
假如我们有一台老旧的win98电脑
public class Windows98Machine {}
但是没有显示器和键盘又有什么用呢?
public class Windows98Machine {
private final StandardKeyboard keyboard;
private final Monitor monitor;
public Windows98Machine() {
monitor = new Monitor();
keyboard = new StandardKeyboard();
}
}
现在我们有了一台完整的电脑,但是我们在98类中声明了StandardKeyboard和Monitor,这就把3个类耦合在一起了,我们也因此失去了切换键盘/显示器的能力。
那么我们把他们解耦
public interface Keyboard { }
先定义一个键盘接口
public class Windows98Machine{
private final Keyboard keyboard;
private final Monitor monitor;
public Windows98Machine(Keyboard keyboard, Monitor monitor) {
this.keyboard = keyboard;
this.monitor = monitor;
}
}
我们使用了依赖注入方式来添加键盘依赖。还有StrandardKeyboard类要实现Keyboard接口才能注入。显示器类也是同理。
public class StandardKeyboard implements Keyboard { }
现在,我们就能自由的切换键盘和显示器了。