enyang
enyang
Published on 2025-12-06 / 0 Visits
0
0

SOLID 五大设计原则

SOLID 五原则用于指导面向对象设计,目标是让系统:

  • 更易扩展(Extensible)

  • 更易维护(Maintainable)

  • 更易测试(Testable)

  • 更松耦合(Loosely Coupled)

  • 更清晰(Clean & Understandable)

SOLID 包含五条原则:

  1. S — 单一职责原则(Single Responsibility Principle)

  2. O — 开闭原则(Open Closed Principle)

  3. L — 里氏替换原则(Liskov Substitution Principle)

  4. I — 接口隔离原则(Interface Segregation Principle)

  5. D — 依赖倒置原则(Dependency Inversion Principle)

1 单一职责原则(SRP)

一个类应该只有一个理由引起它发生变化。
一个模块应该只负责一个功能。

1.1 为什么要单一职责?

若一个类承担多种职责,则:

  • 修改 A 可能会影响 B

  • 代码难懂、难测试

  • 改动风险大

  • 职责边界混乱

单一职责能实现:

  • 单一变化点

  • 更小的类

  • 高内聚

  • 易扩展易维护

1.2 常见反例

public class UserService {
    public void register(String username) {
        // 注册处理
    }

    public void sendEmail(String username) {
        // 发送邮件
    }
}

UserService 同时负责:

  • 注册业务

  • 邮件通知

违反 SRP。

1.3 正确做法(拆分职责)

public class UserService {
    public void register(String username) {
        // 注册
    }
}

public class EmailService {
    public void sendEmail(String username) {
        // 发邮件
    }
}

每个类只负责一件事。

1.4 SRP 在系统中的体现

常见 SRP 体现方式:

  • 分层架构:Controller、Service、Repository

  • 工具类不混业务逻辑

  • 业务规则与持久化分离

  • Web 层与逻辑层独立

SRP 是重构最常用原则。

2 开闭原则(OCP)

软件实体应该对扩展开放,对修改关闭。

意思:

  • 新需求应该通过扩展新代码实现

  • 而不是通过修改旧代码实现

这样可以降低破坏风险。

2.1 错误示例

折扣策略随用户等级变化。

public class DiscountService {
    public double discount(String level) {
        if (level.equals("VIP")) return 0.8;
        if (level.equals("SuperVIP")) return 0.6;
        return 1.0;
    }
}

增加 GoldVIP 时需要修改源代码,违反 OCP。

2.2 正确示例(使用抽象扩展)

public interface Discount {
    double getRate();
}

public class VipDiscount implements Discount {
    public double getRate() { return 0.8; }
}

public class SuperVipDiscount implements Discount {
    public double getRate() { return 0.6; }
}

新增 GoldVipDiscount,不需修改原逻辑,只需增加类。

2.3 OCP 与设计模式关系

OCP 是策略模式、工厂模式、模板方法、装饰器等设计模式的核心思想。

设计模式几乎都是为了满足 OCP。

3 里氏替换原则(LSP)

子类对象必须能够替换父类对象,并保证程序行为不变。

换句话说:

  • 子类应遵守父类的约定

  • 不能破坏父类的功能

  • 不能改变父类接口的顺序/含义/异常行为

3.1 错误示例

class Rectangle {
    void setWidth(int w);
    void setHeight(int h);
}

class Square extends Rectangle {
    void setWidth(int w) { super.setWidth(w); super.setHeight(w); }
    void setHeight(int h) { super.setWidth(h); super.setHeight(h); }
}

Square 强制宽=高,破坏了 Rectangle 应有的行为。

因此它们不应使用继承关系。

3.2 正确做法

使用接口 Shape:

interface Shape {
    int area();
}

Rectangle 与 Square 都实现 Shape,而不是互相继承。

3.3 LSP 的本质

“行为兼容性”比“结构兼容性”更重要。

继承不是为了复用代码,而是为了建立“is-a”关系(行为一致)。

4 接口隔离原则(ISP)

客户端不应该被迫依赖它不使用的方法。

接口应当细分为更小、更具体的接口。

4.1 常见反例:大接口

public interface Animal {
    void run();
    void fly();
    void swim();
}

Dog 实现 Animal:

class Dog implements Animal {
    public void run() {}
    public void fly() {} // 不需要却被迫实现
    public void swim() {}
}

违反 ISP。

4.2 正确做法(拆接口)

interface Runnable { void run(); }
interface Flyable { void fly(); }
interface Swimmable { void swim(); }

Dog 只实现它需要的:

class Dog implements Runnable, Swimmable {
    ...
}

4.3 ISP 的实际价值

  • 降低接口耦合

  • 增强灵活性

  • 避免“胖接口”问题

  • 提高模块可替换性

5 依赖倒置原则(DIP)

高层模块不应该依赖低层模块,两者应该依赖于抽象。

抽象不应该依赖具体实现,具体实现应该依赖于抽象。

5.1 错误示例(强耦合)

class UserService {
    MySQLRepository repo = new MySQLRepository();
}

UserService 强依赖 MySQL,如果数据库换成 MongoDB,需要改业务代码。

违反 DIP。

5.2 正确做法(依赖接口)

抽象接口:

interface UserRepository {
    void save();
}

具体实现:

class MySQLRepository implements UserRepository { ... }
class MongoRepository implements UserRepository { ... }

高层依赖接口:

class UserService {
    private UserRepository repo;

    public UserService(UserRepository repo) {
        this.repo = repo;
    }
}

这就是 Spring IOC 的思想——依赖倒置。

5.3 DIP 的价值

  • 易测试(可替换 mock)

  • 易扩展(替换实现)

  • 降低耦合(高层不依赖低层细节)

6 SOLID 总结

原则

目的

如何做到

SRP

单一职责

一个类一个功能

OCP

可扩展性

扩展新类,不修改旧代码

LSP

行为兼容

子类完全替代父类

ISP

降低耦合

小接口,多接口

DIP

可替换性

依赖抽象而非具体实现

SOLID 是构建高质量软件的核心思想。

7 额外:SOLID 与设计模式的关系

  • 工厂模式 → DIP + OCP

  • 策略模式 → OCP

  • 装饰器模式 → OCP

  • 模板方法 → OCP

  • 组合模式 → LSP

  • 接口分离 → ISP

  • Spring IOC → DIP


Comment