2. SOLID 原则为何而生?
SOLID 原则最初由 Robert C. Martin(即 Uncle Bob)在 2000 年的论文《Design Principles and Design Patterns》中提出。随后,Michael Feathers 提出了 SOLID 这个缩写词,将这五个设计原则整合成一套易于记忆的体系。
这五个原则在过去二十年间深刻影响了面向对象编程的发展,成为现代软件设计的重要基石。
✅ SOLID 是什么?
SOLID 是以下五个面向对象设计原则的首字母缩写:
- Single Responsibility Principle(单一职责原则)
- Open/Closed Principle(开闭原则)
- Liskov Substitution Principle(里氏替换原则)
- Interface Segregation Principle(接口隔离原则)
- Dependency Inversion Principle(依赖倒置原则)
这些原则的核心目标是:提升代码的可维护性、可理解性和灵活性,从而在项目不断扩大的过程中,有效降低复杂度,减少后期维护成本。
接下来我们将逐一深入探讨这些原则,并通过 Java 示例加以说明。
3. 单一职责原则(Single Responsibility Principle)
顾名思义,一个类应该只有一个引起它变化的原因,即一个类只负责一项职责。
✅ 为什么重要?
- 测试更简单:职责单一的类功能明确,测试用例更少。
- 耦合度更低:功能分散,减少模块之间的依赖。
- 结构更清晰:代码组织更合理,查找和维护更容易。
示例:Book 类的设计
public class Book {
private String name;
private String author;
private String text;
// constructor, getters and setters
}
接着我们为 Book 添加两个方法用于文本处理:
public class Book {
private String name;
private String author;
private String text;
// constructor, getters and setters
public String replaceWordInText(String word, String replacementWord){
return text.replaceAll(word, replacementWord);
}
public boolean isWordInText(String word){
return text.contains(word);
}
}
看起来不错。但如果我们在 Book 类中直接加入打印方法:
public class BadBook {
//...
void printTextToConsole(){
// 格式化并打印文本
}
}
这就违反了单一职责原则 ❌。打印逻辑与 Book 的核心职责无关。
✅ 正确做法:拆分职责
public class BookPrinter {
void printTextToConsole(String text){
// 打印到控制台
}
void printTextToAnotherMedium(String text){
// 输出到其他介质
}
}
这样,Book 类专注于内容管理,而 BookPrinter 负责展示逻辑。✅
4. 开闭原则(Open/Closed Principle)
对扩展开放,对修改封闭
也就是说,在不修改现有代码的前提下,允许系统行为扩展。这是避免引入新 bug 的关键手段。
示例:Guitar 类的增强
public class Guitar {
private String make;
private String model;
private int volume;
// Constructors, getters & setters
}
如果想给 Guitar 添加火焰图案,不要直接修改 Guitar 类 ❌,而是通过继承扩展:
public class SuperCoolGuitarWithFlames extends Guitar {
private String flameColor;
// constructor, getters + setters
}
这样,原有功能不受影响,新功能也顺利加入 ✅。
5. 里氏替换原则(Liskov Substitution Principle)
子类对象应该能够替换父类对象,而不会影响程序的正确性
示例:Car 接口的实现
public interface Car {
void turnOnEngine();
void accelerate();
}
传统燃油车实现:
public class MotorCar implements Car {
private Engine engine;
public void turnOnEngine() {
engine.on();
}
public void accelerate() {
engine.powerOn(1000);
}
}
电动车的实现却出现问题:
public class ElectricCar implements Car {
public void turnOnEngine() {
throw new AssertionError("I don't have an engine!");
}
public void accelerate() {
// 电动车加速逻辑
}
}
这明显违反了 Liskov 替换原则 ❌。电动车不能替换燃油车而不破坏程序行为。
✅ 解决方案:
重新设计接口,将发动机相关的逻辑分离出来,避免强制所有子类实现无意义的方法。
6. 接口隔离原则(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 BearCarer implements BearCleaner, BearFeeder {
public void washTheBear() {
// 洗熊
}
public void feedTheBear() {
// 喂熊
}
}
而那些不怕死的人可以单独实现:
public class CrazyPerson implements BearPetter {
public void petTheBear() {
// 祝你好运!
}
}
✅ 通过接口隔离,我们可以避免“被迫实现”不需要的方法。
7. 依赖倒置原则(Dependency Inversion Principle)
高层模块不应该依赖低层模块,两者都应该依赖抽象
示例:Windows 98 电脑的构造
public class Windows98Machine {
private final StandardKeyboard keyboard;
private final Monitor monitor;
public Windows98Machine() {
monitor = new Monitor();
keyboard = new StandardKeyboard();
}
}
这种写法直接耦合了具体类 ❌。
✅ 正确做法:依赖注入 + 抽象接口
public interface Keyboard { }
public class StandardKeyboard implements Keyboard { }
public class Windows98Machine {
private final Keyboard keyboard;
private final Monitor monitor;
public Windows98Machine(Keyboard keyboard, Monitor monitor) {
this.keyboard = keyboard;
this.monitor = monitor;
}
}
现在我们可以通过构造函数注入不同的键盘实现,系统变得灵活又易于测试 ✅。
8. 总结
本文我们深入探讨了 SOLID 五大原则,并通过 Java 示例展示了如何避免常见设计陷阱。
- 单一职责:让类职责更清晰
- 开闭原则:扩展不改旧代码
- 里氏替换:子类能安全替换父类
- 接口隔离:接口按需拆分
- 依赖倒置:模块之间解耦
这些原则不是教条,而是帮助我们写出更易维护、扩展和测试的高质量代码的指导方针。
✅ 示例代码已上传至 GitHub。