Skip to content

SOLID 是面向对象设计的五个原则,每一个都是"避免某类坏味道"的经验总结。这篇用坏例子 → 好例子 → 一句话记住的方式把五个讲透(Java 示例,全部编译验证过)。

SOLID 五大设计原则

一、S:单一职责(Single Responsibility)

一个类只干一件事——一个类只有一个"需要修改它的理由"。

java
// ❌ 坏:一个类干了三件事(用户业务 + 发邮件 + 存 Excel)
class UserService {
    void createUser(String name) { /* 业务 */ }
    void sendWelcomeEmail(String email) { /* 邮件 */ }
    void exportUserExcel() { /* Excel */ }
}
// 改邮件逻辑要动这个类,改 Excel 也要动——牵一发动全身

// ✅ 好:三个类各管一件事
class UserService { void createUser(String name) { } }          // 只管业务
class EmailService { void sendWelcomeEmail(String email) { } }  // 只管邮件
class ExcelExporter { void exportUserExcel() { } }              // 只管导出

一句话记住:"一个类一个理由去改"——职责混在一起,改 A 容易碰坏 B。

二、O:开闭原则(Open-Closed)

对扩展开放,对修改关闭——加新功能时不改老代码,而是扩展新代码。

java
// ❌ 坏:加新优惠要改老方法(if-else 越堆越多)
class DiscountService {
    double calc(double price, int type) {
        if (type == 1) return price * 0.9;
        if (type == 2) return price * 0.8;
        return price;   // 加 type==3 又要改这里!
    }
}

// ✅ 好:策略接口 + 实现类,加新优惠 = 新增一个类,老代码不动
interface Discount {
    double calc(double price);
}
class VipDiscount implements Discount { public double calc(double p) { return p * 0.9; } }
class GoldDiscount implements Discount { public double calc(double p) { return p * 0.8; } }
// 新优惠:class NewDiscount implements Discount { ... }  ← 只加不改

一句话记住:"加功能别改老代码"——这是策略模式(如有多篇)和模板方法的根基。

三、L:里氏替换(Liskov Substitution)

子类能替换父类且不破坏行为——继承别破坏父类的约定。

java
// ❌ 坏:正方形继承长方形,破坏了"宽高独立"的约定
class Rectangle {
    protected int w, h;
    void setW(int w) { this.w = w; }
    void setH(int h) { this.h = h; }
    int area() { return w * h; }
}
class Square extends Rectangle {
    void setW(int w) { this.w = w; this.h = w; }  // 改了语义!
    void setH(int h) { this.h = h; this.w = h; }
}
// 用父类接口操作时:r.setW(5); r.setH(4); r.area() 对 Square 变成 16(应 20)——行为被破坏

// ✅ 好:正方形不继承长方形(继承"形状"接口),各自实现
interface Shape { int area(); }
class Rect implements Shape { /* w*h */ }
class Sq implements Shape { /* side*side */ }

一句话记住:"子类别比父类更弱"——继承是"is-a"关系,子类必须能完整替代父类。

四、I:接口隔离(Interface Segregation)

接口别太大,拆成小接口——不要强迫实现方实现用不到的方法。

java
// ❌ 坏:胖接口,飞鸟要实现 swim(),鱼要实现 fly()
interface Animal {
    void eat();
    void fly();   // 鱼:??
    void swim();  // 鸟:??
}
class Bird implements Animal { /* swim() 只能空实现 */ }

// ✅ 好:拆成小接口,按能力实现
interface Eater { void eat(); }
interface Flyer { void fly(); }
interface Swimmer { void swim(); }
class Bird implements Eater, Flyer { }   // 只实现会用的
class Fish implements Eater, Swimmer { }

一句话记住:"胖接口拆成小接口"——客户端只依赖它用的那部分。

五、D:依赖倒置(Dependency Inversion)

依赖抽象,不依赖实现——接口定义在上层(核心),实现放下层。

java
// ❌ 坏:Service 直接 new 具体 DB 实现,换数据库要改业务代码
class UserService {
    private MysqlUserDao dao = new MysqlUserDao();  // 依赖具体类
}

// ✅ 好:依赖接口,具体实现从外部注入(构造器)
interface UserRepository {
    User findByName(String name);
}
class MysqlUserRepository implements UserRepository { }
class UserService {
    private final UserRepository repo;   // 依赖抽象
    UserService(UserRepository repo) { this.repo = repo; }  // 构造器注入
}
// 换数据库:new UserService(new MongoUserRepository()) —— 业务代码零改动

一句话记住:"依赖抽象不依赖实现"——这正是《DDD 分层架构》洋葱图里"领域层不依赖任何框架和数据库"的实现机制(Repository 接口在领域层、实现在基础设施层)。

六、五个原则怎么配合

原则防什么坏味道和谁配合
S 单一职责上帝类(什么都会)拆分 + 接口隔离
O 开闭原则改老代码出 bug策略模式/模板方法
L 里氏替换继承破坏行为多用组合少用继承
I 接口隔离胖接口、空实现接口按能力拆分
D 依赖倒置核心依赖具体实现DI/IoC + DDD 分层

实际使用:设计新类时逐条过一遍(职责单一吗?扩展要改老代码吗?继承安全吗?接口太大吗?依赖抽象吗?)——五个全过,代码通常就差不了。不用刻意追求完美,过度拆分也是坏味道(见《编程思想》栏目其他文章)。

小结

  • S:一个类一个职责(一个理由去改)
  • O:扩展开放、修改关闭(加功能不改老代码)
  • L:子类可替换父类(继承别破坏约定)
  • I:接口隔离(胖接口拆小)
  • D:依赖倒置(依赖抽象不依赖实现——DDD 洋葱图机制)

想深入理解设计模式的落地,看《设计模式》栏目;想理解依赖倒置在架构层面的应用,看《DDD 分层架构》。