2. SOLID 原则为何而生?

SOLID 原则最初由 Robert C. Martin(即 Uncle Bob)在 2000 年的论文《Design Principles and Design Patterns》中提出。随后,Michael Feathers 提出了 SOLID 这个缩写词,将这五个设计原则整合成一套易于记忆的体系。

这五个原则在过去二十年间深刻影响了面向对象编程的发展,成为现代软件设计的重要基石。

✅ SOLID 是什么?

SOLID 是以下五个面向对象设计原则的首字母缩写:

  1. Single Responsibility Principle(单一职责原则)
  2. Open/Closed Principle(开闭原则)
  3. Liskov Substitution Principle(里氏替换原则)
  4. Interface Segregation Principle(接口隔离原则)
  5. 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


原始标题:A Solid Guide to SOLID Principles | Baeldung