面向对象设计的SOLID原则

核心概念

面向对象设计原则(SOLID原则)是面向对象编程和设计中的基本准则,旨在创建更容易维护、扩展和理解的软件系统。这些原则帮助开发者编写高质量的面向对象代码。


一、单一职责原则(SRP)

1.1 定义

一个类应该只有一个改变的理由。

1.2 错误示例

// ❌ 违反单一职责
class UserManager {
    // 同时处理用户数据和发送通知
    public void createUser(User user) {
        // 保存用户
        userRepository.save(user);

        // 发送通知(职责混合)
        emailService.sendWelcomeEmail(user);
    }
}

1.3 正确设计

// ✅ 遵循单一职责
class UserRepository {
    public void save(User user) {
        // 仅负责数据持久化
    }
}

class UserNotificationService {
    public void sendWelcomeEmail(User user) {
        // 仅负责发送通知
    }
}

class UserService {
    private UserRepository userRepository;
    private UserNotificationService notificationService;

    public void createUser(User user) {
        userRepository.save(user);
        notificationService.sendWelcomeEmail(user);
    }
}

1.4 优点

  • 提高代码可读性
  • 降低类的复杂性
  • 提高代码的可维护性
  • 便于单元测试

二、开闭原则(OCP)

2.1 定义

软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

2.2 错误示例

// ❌ 违反开闭原则
class AreaCalculator {
    public double calculateArea(Shape shape) {
        if (shape instanceof Rectangle) {
            Rectangle rect = (Rectangle) shape;
            return rect.getWidth() * rect.getHeight();
        } else if (shape instanceof Circle) {
            Circle circle = (Circle) shape;
            return Math.PI * circle.getRadius() * circle.getRadius();
        }
        // 每增加新形状都要修改这个方法
    }
}

2.3 正确设计

// ✅ 遵循开闭原则
interface Shape {
    double calculateArea();
}

class Rectangle implements Shape {
    private double width;
    private double height;

    @Override
    public double calculateArea() {
        return width * height;
    }
}

class Circle implements Shape {
    private double radius;

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

class AreaCalculator {
    public double calculateTotalArea(List<Shape> shapes) {
        return shapes.stream()
                     .mapToDouble(Shape::calculateArea)
                     .sum();
    }
}

2.4 实现技术

  • 使用抽象类和接口
  • 依赖倒置原则
  • 策略模式
  • 模板方法模式

三、里氏替换原则(LSP)

3.1 定义

所有引用基类的地方必须能透明地使用其子类的对象。

3.2 错误示例

// ❌ 违反里氏替换原则
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;
    }
}

class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        super.width = width;
        super.height = width;  // 破坏了Rectangle的行为
    }

    @Override
    public void setHeight(int height) {
        super.width = height;
        super.height = height;  // 破坏了Rectangle的行为
    }
}

// 使用
void resizeRectangle(Rectangle rect) {
    rect.setWidth(5);
    rect.setHeight(10);
    // 期望面积是50,但Square会得到100
    assert rect.getArea() == 50;  // 失败
}

3.3 正确设计

// ✅ 遵循里氏替换原则
interface Shape {
    int getArea();
}

class Rectangle implements Shape {
    protected int width;
    protected int height;

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public int getArea() {
        return width * height;
    }
}

class Square implements Shape {
    private int side;

    public Square(int side) {
        this.side = side;
    }

    @Override
    public int getArea() {
        return side * side;
    }
}

3.4 关键点

  • 子类不应该改变父类的行为
  • 子类必须能替换父类
  • 避免继承带来的副作用

四、接口隔离原则(ISP)

4.1 定义

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

4.2 错误示例

// ❌ 臃肿的接口
interface Worker {
    void work();
    void eat();
    void sleep();
    void code();
    void manage();
}

// 不同角色被迫实现不需要的方法
class Programmer implements Worker {
    public void work() { /* 编程 */ }
    public void code() { /* 编码 */ }

    // 不相关的方法也必须实现
    public void manage() { throw new UnsupportedOperationException(); }
    public void eat() { }
    public void sleep() { }
}

4.3 正确设计

// ✅ 细粒度的接口
interface Workable {
    void work();
}

interface Codeable {
    void code();
}

interface Manageable {
    void manage();
}

class Programmer implements Workable, Codeable {
    public void work() { /* 工作 */ }
    public void code() { /* 编码 */ }
}

class Manager implements Workable, Manageable {
    public void work() { /* 工作 */ }
    public void manage() { /* 管理 */ }
}

4.4 优点

  • 降低耦合
  • 提高系统灵活性
  • 便于重构
  • 提高代码可读性

五、依赖倒置原则(DIP)

5.1 定义

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

5.2 错误示例

// ❌ 高层直接依赖具体实现
class EmailService {
    public void sendEmail(String message) {
        // 发送邮件
    }
}

class NotificationService {
    private EmailService emailService;

    public NotificationService() {
        this.emailService = new EmailService();
    }

    public void sendNotification(String message) {
        emailService.sendEmail(message);
    }
}

5.3 正确设计

// ✅ 依赖抽象
interface MessageService {
    void send(String message);
}

class EmailService implements MessageService {
    public void send(String message) {
        // 发送邮件
    }
}

class SMSService implements MessageService {
    public void send(String message) {
        // 发送短信
    }
}

class NotificationService {
    private MessageService messageService;

    // 通过构造函数/依赖注入传入抽象
    public NotificationService(MessageService service) {
        this.messageService = service;
    }

    public void sendNotification(String message) {
        messageService.send(message);
    }
}

5.4 实现方式

  • 依赖注入
  • 接口编程
  • 策略模式

六、最佳实践

6.1 设计原则应用

// 综合运用SOLID原则的示例
public interface PaymentStrategy {
    void pay(double amount);
}

public class CreditCardPayment implements PaymentStrategy {
    private CreditCardValidator validator;
    private TransactionLogger logger;

    // 依赖注入
    public CreditCardPayment(
        CreditCardValidator validator,
        TransactionLogger logger
    ) {
        this.validator = validator;
        this.logger = logger;
    }

    @Override
    public void pay(double amount) {
        // 单一职责:仅处理信用卡支付
        validator.validate(amount);
        // 处理支付逻辑
        logger.log(amount);
    }
}

6.2 注意事项

  • 不要过度设计
  • 根据实际需求应用原则
  • 保持代码简洁和可读
  • 持续重构

七、常见问题

Q1: 这些原则是否必须严格遵守?

答:

  • 原则是指导,不是教条
  • 根据具体场景灵活运用
  • 过度遵守可能导致过度复杂的设计

Q2: 如何学习和应用这些原则?

答:

  • 阅读优秀的开源代码
  • 重构现有代码
  • 持续学习和实践
  • 理解设计意图,而不是机械遵守

相关链接


记忆口诀

单一职责不混搭,一个类只做一件事
开闭原则要灵活,扩展开放修改闭
里氏替换不破坏,子类行为要一致
接口隔离去臃肿,小而精准是真谛
依赖倒置解耦合,面向抽象不面向
设计原则是指引,灵活运用显真功

⬅️ 面向对象编程(OOP) 🏠 00-Java ➡️ final关键字