SOLID 五大原则

SOLID 是面向对象设计中最著名的五个设计原则的首字母缩写,由 Robert C. Martin(Uncle Bob)提出。遵循这些原则可以创建更易维护、更灵活、更易扩展的代码。


1. S - 单一职责原则(Single Responsibility Principle, SRP)

核心思想

一个类应该只有一个引起它变化的原因

换句话说:一个类只负责一项职责

❌ 违反 SRP 的例子

public class Order {
    public void createOrder(OrderData data) {
        // 创建订单逻辑
    }
    
    public void calculatePrice(Order order) {
        // 价格计算逻辑
    }
    
    public void sendEmail(Order order) {
        // 发送邮件逻辑
    }
    
    public void saveToDatabase(Order order) {
        // 数据库操作逻辑
    }
}

问题: 这个类有 4 个职责,任何一个职责变化都会导致类被修改

✅ 遵循 SRP 的例子

// 订单实体
public class Order {
    private String id;
    private double price;
    // ...
}

// 订单服务 - 负责业务逻辑
public class OrderService {
    public void createOrder(OrderData data) { }
    public void calculatePrice(Order order) { }
}

// 邮件服务 - 负责通知
public class EmailService {
    public void sendOrderConfirmation(Order order) { }
}

// 数据访问 - 负责持久化
public class OrderRepository {
    public void save(Order order) { }
}

2. O - 开闭原则(Open-Closed Principle, OCP)

核心思想

对扩展开放,对修改关闭

软件实体(类、模块、函数等)应该在不修改现有代码的情况下进行扩展

❌ 违反 OCP 的例子

public class PaymentProcessor {
    public void processPayment(String paymentType, double amount) {
        if ("alipay".equals(paymentType)) {
            // 支付宝支付逻辑
        } else if ("wechat".equals(paymentType)) {
            // 微信支付逻辑
        } else if ("bank".equals(paymentType)) {
            // 银行支付逻辑
        }
        // 每新增一种支付方式,都要修改这个方法!
    }
}

✅ 遵循 OCP 的例子

// 抽象接口
public interface PaymentStrategy {
    void pay(double amount);
}

// 具体实现
public class AlipayStrategy implements PaymentStrategy {
    public void pay(double amount) {
        // 支付宝支付逻辑
    }
}

public class WechatPayStrategy implements PaymentStrategy {
    public void pay(double amount) {
        // 微信支付逻辑
    }
}

// 支付处理器 - 不需要修改
public class PaymentProcessor {
    public void process(PaymentStrategy strategy, double amount) {
        strategy.pay(amount);
    }
}

// 使用时扩展即可,无需修改现有代码
PaymentProcessor processor = new PaymentProcessor();
processor.process(new AlipayStrategy(), 100.0);

3. L - 里氏替换原则(Liskov Substitution Principle, LSP)

核心思想

子类必须能够替换它们的基类而不影响程序的正确性

子类应该扩展父类的功能,而不是改变父类的功能

❌ 违反 LSP 的例子

public class Rectangle {
    protected int width;
    protected int height;
    
    public void setWidth(int width) {
        this.width = width;
    }
    
    public void setHeight(int height) {
        this.height = height;
    }
    
    public int getArea() {
        return width * height;
    }
}

// 正方形继承长方形 - 违反了 LSP
public class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width; // 强制宽高相等
    }
    
    @Override
    public void setHeight(int height) {
        this.width = height;
        this.height = height; // 强制宽高相等
    }
}

// 使用场景 - 用正方形替换长方形会出问题
public void testRectangle(Rectangle rect) {
    rect.setWidth(5);
    rect.setHeight(10);
    System.out.println(rect.getArea()); 
    // 期望:50
    // 如果传入 Square:100 (行为被改变了!)
}

✅ 遵循 LSP 的例子

// 使用接口而不是继承
public interface Shape {
    int getArea();
}

public class Rectangle implements Shape {
    private int width;
    private int height;
    
    public void setWidth(int width) { this.width = width; }
    public void setHeight(int height) { this.height = height; }
    public int getArea() { return width * height; }
}

public class Square implements Shape {
    private int side;
    
    public void setSide(int side) { this.side = side; }
    public int getArea() { return side * side; }
}

4. I - 接口隔离原则(Interface Segregation Principle, ISP)

核心思想

客户端不应该依赖它不需要的接口

多个特定的接口比一个通用的接口更好

❌ 违反 ISP 的例子

// 臃肿的接口
public interface Worker {
    void work();
    void eat();
    void sleep();
    void attendMeeting();
    void writeReport();
}

// 机器人不需要吃饭和睡觉,但被迫实现
public class Robot implements Worker {
    public void work() { /* 工作 */ }
    public void eat() { throw new UnsupportedOperationException(); }
    public void sleep() { throw new UnsupportedOperationException(); }
    public void attendMeeting() { /* 参加会议 */ }
    public void writeReport() { /* 写报告 */ }
}

✅ 遵循 ISP 的例子

// 拆分成多个小接口
public interface Workable {
    void work();
}

public interface Eatable {
    void eat();
}

public interface Sleepable {
    void sleep();
}

// 人类实现需要的接口
public class Human implements Workable, Eatable, Sleepable {
    public void work() { /* 工作 */ }
    public void eat() { /* 吃饭 */ }
    public void sleep() { /* 睡觉 */ }
}

// 机器人只实现需要的接口
public class Robot implements Workable {
    public void work() { /* 工作 */ }
}

5. D - 依赖倒置原则(Dependency Inversion Principle, DIP)

这个原则我刚才已经详细介绍过了,这里简要回顾:

核心思想

  • 高层模块不应该依赖低层模块,两者都应该依赖抽象
  • 抽象不应该依赖细节,细节应该依赖抽象

关键点

  • 面向接口编程,而不是面向实现编程
  • 扩展时只需修改配置/工厂,不需要改动业务代码

SOLID 原则总结

原则缩写一句话总结解决的问题
单一职责原则SRP一个类只做一件事类过于臃肿,难以维护
开闭原则OCP对扩展开放,对修改关闭频繁修改稳定代码导致 Bug
里氏替换原则LSP子类能无缝替换父类继承导致的行为异常
接口隔离原则ISP接口要小而专,不要大而全被迫实现不需要的方法
依赖倒置原则DIP依赖抽象,不依赖具体实现代码耦合度高,难以扩展

SOLID 原则之间的关系

SRP(单一职责) → 指导如何拆分类

OCP(开闭原则) → 拆分后如何扩展

LSP(里氏替换) → 继承时如何保证正确性

ISP(接口隔离) → 如何设计接口

DIP(依赖倒置) → 如何降低模块间耦合

共同目标: 创建高内聚、低耦合、易维护、易扩展的软件系统