知识模块
☕ Java 知识模块
十一、设计模式
SOLID 原则

SOLID 原则

SOLID 是面向对象设计的五大基本原则,帮助编写可维护、可扩展的代码。

一、SOLID 概览

原则名称核心思想
S单一职责原则 (SRP)一个类只负责一件事
O开闭原则 (OCP)对扩展开放,对修改关闭
L里氏替换原则 (LSP)子类可以替换父类
I接口隔离原则 (ISP)接口要小而专一
D依赖倒置原则 (DIP)依赖抽象,不依赖具体

二、单一职责原则 (SRP)

Single Responsibility Principle

定义:一个类应该只有一个引起它变化的原因。

违反 SRP

// 一个类负责太多事情
public class UserService {
    public void register(User user) { }
    public void sendEmail(User user) { }    // 邮件发送
    public void generateReport() { }        // 报告生成
    public void saveToDatabase(User user) { } // 数据库操作
}

遵循 SRP

// 用户服务:只负责用户相关
public class UserService {
    public void register(User user) { }
}
 
// 邮件服务:只负责邮件发送
public class EmailService {
    public void sendEmail(User user) { }
}
 
// 报告服务:只负责报告生成
public class ReportService {
    public void generateReport() { }
}
 
// 数据访问:只负责数据库操作
public class UserRepository {
    public void save(User user) { }
}

判断标准

如果类有多个变化原因 → 违反 SRP
例:UserService 变化原因:
1. 用户注册逻辑变化
2. 邮件格式变化
3. 报告格式变化
→ 应该拆分

三、开闭原则 (OCP)

Open/Closed Principle

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

违反 OCP

// 每次新增形状都要修改 AreaCalculator
public class AreaCalculator {
    public double calculate(Object shape) {
        if (shape instanceof Circle) {
            return Math.PI * ((Circle) shape).getRadius() * ((Circle) shape).getRadius();
        } else if (shape instanceof Rectangle) {
            return ((Rectangle) shape).getWidth() * ((Rectangle) shape).getHeight();
        }
        // 新增形状需要修改这里
        return 0;
    }
}

遵循 OCP

// 抽象
public interface Shape {
    double calculateArea();
}
 
// 具体实现
public class Circle implements Shape {
    private double radius;
    
    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}
 
public class Rectangle implements Shape {
    private double width;
    private double height;
    
    @Override
    public double calculateArea() {
        return width * height;
    }
}
 
// 计算器无需修改
public class AreaCalculator {
    public double calculate(Shape shape) {
        return shape.calculateArea();
    }
}
 
// 新增形状无需修改 AreaCalculator
public class Triangle implements Shape {
    @Override
    public double calculateArea() {
        // 三角形面积计算
    }
}

关键技巧

  • 使用抽象和多态
  • 策略模式
  • 工厂模式

四、里氏替换原则 (LSP)

Liskov Substitution Principle

定义:子类对象必须能够替换掉所有父类对象,而不影响程序正确性。

违反 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; }
}
 
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;  // 强制正方形
    }
}
 
// 问题:用 Square 替换 Rectangle 会改变行为
void resize(Rectangle r) {
    r.setWidth(5);
    r.setHeight(4);
    // 对于 Rectangle,面积应该是 20
    // 对于 Square,面积是 16(违反预期)
}

遵循 LSP

// 抽象出共同的父类
public abstract class Shape {
    public abstract int getArea();
}
 
public class Rectangle extends Shape {
    private int width;
    private int height;
    
    @Override
    public int getArea() { return width * height; }
}
 
public class Square extends Shape {
    private int side;
    
    @Override
    public int getArea() { return side * side; }
}

LSP 检查

能否用子类替换父类而不影响程序行为?
是 → 符合 LSP
否 → 违反 LSP

五、接口隔离原则 (ISP)

Interface Segregation Principle

定义:客户端不应该依赖它不需要的接口。

违反 ISP

// 臃肿的接口
public interface Worker {
    void work();
    void eat();
    void sleep();
}
 
// 机器人不需要吃和睡
public class Robot implements Worker {
    @Override
    public void work() { /* 工作 */ }
    
    @Override
    public void eat() { 
        throw new UnsupportedOperationException();  // 被迫实现不需要的方法
    }
    
    @Override
    public void sleep() { 
        throw new UnsupportedOperationException();  // 被迫实现不需要的方法
    }
}

遵循 ISP

// 拆分为小接口
public interface Workable {
    void work();
}
 
public interface Eatable {
    void eat();
}
 
public interface Sleepable {
    void sleep();
}
 
// 人类实现所有接口
public class Human implements Workable, Eatable, Sleepable {
    @Override
    public void work() { /* 工作 */ }
    
    @Override
    public void eat() { /* 吃饭 */ }
    
    @Override
    public void sleep() { /* 睡觉 */ }
}
 
// 机器人只实现需要的接口
public class Robot implements Workable {
    @Override
    public void work() { /* 工作 */ }
}

接口设计原则

接口要小而专一
一个接口只服务于一个客户端
避免"胖"接口

六、依赖倒置原则 (DIP)

Dependency Inversion Principle

定义

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

违反 DIP

// 高层模块直接依赖低层模块
public class UserService {
    private MySQLDatabase database = new MySQLDatabase();  // 直接依赖具体实现
    
    public void save(User user) {
        database.save(user);
    }
}
 
// 切换数据库需要修改 UserService

遵循 DIP

// 抽象
public interface Database {
    void save(User user);
}
 
// 低层模块
public class MySQLDatabase implements Database {
    @Override
    public void save(User user) {
        // MySQL 保存逻辑
    }
}
 
public class PostgreSQLDatabase implements Database {
    @Override
    public void save(User user) {
        // PostgreSQL 保存逻辑
    }
}
 
// 高层模块依赖抽象
public class UserService {
    private Database database;
    
    // 依赖注入
    public UserService(Database database) {
        this.database = database;
    }
    
    public void save(User user) {
        database.save(user);
    }
}
 
// 使用
UserService service = new UserService(new MySQLDatabase());
// 或
UserService service = new UserService(new PostgreSQLDatabase());

Spring 依赖注入

@Service
public class UserService {
    private final UserRepository repository;
    
    // 构造器注入(推荐)
    public UserService(UserRepository repository) {
        this.repository = repository;
    }
}
 
// 或使用 @Autowired(字段注入,不推荐)
@Service
public class UserService {
    @Autowired
    private UserRepository repository;
}

七、原则关系图

┌─────────────────────────────────────────────────────────┐
│                       SOLID 原则                         │
├─────────────────────────────────────────────────────────┤
│                                                          │
│   ┌─────────────┐                                       │
│   │ SRP 单一职责 │──────→ 类的粒度                       │
│   └─────────────┘                                       │
│          │                                              │
│          ↓                                              │
│   ┌─────────────┐                                       │
│   │ OCP 开闭原则 │──────→ 扩展机制                       │
│   └─────────────┘                                       │
│          │                                              │
│          ↓                                              │
│   ┌─────────────┐                                       │
│   │ LSP 里氏替换 │──────→ 继承关系                       │
│   └─────────────┘                                       │
│          │                                              │
│          ↓                                              │
│   ┌─────────────┐                                       │
│   │ ISP 接口隔离 │──────→ 接口设计                       │
│   └─────────────┘                                       │
│          │                                              │
│          ↓                                              │
│   ┌─────────────┐                                       │
│   │ DIP 依赖倒置 │──────→ 依赖关系                       │
│   └─────────────┘                                       │
│                                                          │
└─────────────────────────────────────────────────────────┘

八、面试高频问题

Q1: 为什么 SRP 很重要?

  • 降低类复杂度
  • 提高可读性和可维护性
  • 降低变更风险

Q2: 如何实现 OCP?

  • 使用抽象和多态
  • 使用设计模式(策略、工厂、装饰器)
  • 使用接口定义行为

Q3: LSP 的核心是什么?

  • 子类必须完全实现父类行为
  • 子类可以有自己的扩展行为
  • 子类不能破坏父类的行为约束

Q4: ISP 和 DIP 有什么关系?

  • ISP 关注接口大小
  • DIP 关注依赖方向
  • 两者都强调抽象的重要性

Q5: Spring 如何体现 SOLID?

原则Spring 体现
SRP每个 Bean 职责单一
OCP通过接口扩展
LSP接口实现可替换
ISP接口细粒度设计
DIP依赖注入

九、最佳实践

1. 代码审查清单

□ 类是否只有一个变化原因?
□ 是否可以通过扩展而非修改来添加功能?
□ 子类能否完全替换父类?
□ 接口是否足够小?
□ 是否依赖抽象而非具体实现?

2. 重构技巧

违反 SRP → 拆分类
违反 OCP → 提取抽象
违反 LSP → 重新设计继承关系
违反 ISP → 拆分接口
违反 DIP → 引入抽象层

3. 平衡原则

不要过度设计
根据实际需求权衡
迭代中逐步优化

更新时间:2026年3月16日