目录

Java 设计原则详解

掌握 SOLID 原则与常用设计原则,写出高质量可维护的代码

概述

设计原则是软件设计的基石,它们指导开发者编写出高质量、可维护、可扩展的代码。本文将介绍最常用的设计原则,包括 SOLID 原则和其他重要原则。

SOLID 原则概览

SOLID 是面向对象设计的五个基本原则的首字母缩写,由 Robert C. Martin(Uncle Bob)提出。

SOLID 原则

SOLID 是面向对象设计的五个基本原则的首字母缩写,由 Robert C. Martin(Uncle Bob)提出。

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

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

原理说明

如果一个类承担过多的职责,就会导致:

  • 代码耦合度高
  • 可维护性差
  • 难以测试和复用

代码示例

// ❌ 违反 SRP:UserService 处理了太多职责
public class UserService {
    public void createUser(User user) { }
    public void sendEmail(User user, String message) { }
    public void generateReport(User user) { }
    public void logActivity(String action) { }
}

// ✅ 符合 SRP:职责分离
public class UserService {
    private UserRepository userRepository;
    
    public void createUser(User user) {
        userRepository.save(user);
    }
    
    public User getUser(Long id) {
        return userRepository.findById(id);
    }
}

public class EmailService {
    public void sendEmail(String to, String subject, String content) {
        // 发送邮件逻辑
    }
}

public class ReportService {
    public Report generateUserReport(User user) {
        // 生成报告逻辑
        return new Report();
    }
}

public class LoggerService {
    public void log(String level, String message) {
        // 日志记录逻辑
    }
}

框架中的应用

Spring Framework

  • UserDetailsService 只负责用户认证加载
  • PasswordEncoder 只负责密码加密
  • AuthenticationProvider 只负责认证逻辑

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

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

原理说明

  • 通过扩展来增加新功能,而不是修改已有代码
  • 降低引入 bug 的风险
  • 提高代码的可维护性

代码示例

// ❌ 违反 OCP:每次新增支付方式都要修改代码
public class PaymentProcessor {
    public void process(String type, double amount) {
        if (type.equals("ALIPAY")) {
            // 支付宝支付逻辑
        } else if (type.equals("WECHAT")) {
            // 微信支付逻辑
        }
        // 新增支付方式需要修改此处
    }
}

// ✅ 符合 OCP:通过扩展添加新支付方式
public interface PaymentStrategy {
    void pay(double amount);
    boolean supports(String paymentType);
}

@Component
public class AlipayStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("支付宝支付: " + amount);
    }
    
    @Override
    public boolean supports(String type) {
        return "ALIPAY".equals(type);
    }
}

@Component
public class WechatStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("微信支付: " + amount);
    }
    
    @Override
    public boolean supports(String type) {
        return "WECHAT".equals(type);
    }
}

@Service
public class PaymentService {
    private final List<PaymentStrategy> strategies;
    
    public PaymentService(List<PaymentStrategy> strategies) {
        this.strategies = strategies;
    }
    
    public void processPayment(String type, double amount) {
        PaymentStrategy strategy = strategies.stream()
            .filter(s -> s.supports(type))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException("不支持的支付方式"));
        strategy.pay(amount);
    }
}

框架中的应用

Spring Boot 自动配置

  • 通过添加新的 starter 依赖扩展功能
  • 通过 spring.factories 注册新的自动配置类
  • 无需修改 Spring Boot 核心代码

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

子类应该能够替换其父类并且不会导致程序出错。

原理说明

  • 子类必须完全实现父类的方法
  • 子类可以有自己的个性
  • 覆盖或实现父类的方法时,输入参数可以更宽松,输出参数可以更严格

代码示例

// ❌ 违反 LSP:Square 改变了 Rectangle 的行为
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;
    }
}

// 测试时会出现问题
public void testRectangle(Rectangle rect) {
    rect.setWidth(5);
    rect.setHeight(4);
    assert rect.getArea() == 20; // Square 会失败
}

// ✅ 符合 LSP:正确的继承设计
public interface Shape {
    int getArea();
}

public class Rectangle implements Shape {
    private int width;
    private int height;
    
    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }
    
    @Override
    public int getArea() {
        return width * height;
    }
}

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

框架中的应用

Java Collections

  • ArrayListLinkedList 可以替换 List
  • HashSetTreeSet 可以替换 Set
  • 客户端代码使用接口,不关心具体实现

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

客户端不应该被迫依赖它们不使用的接口。

原理说明

  • 大接口拆分成小接口
  • 降低类之间的耦合度
  • 提高接口的内聚性

代码示例

// ❌ 违反 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() { }
}

框架中的应用

Spring Data JPA

  • CrudRepository:基本的 CRUD 操作
  • PagingAndSortingRepository:分页和排序
  • JpaRepository:JPA 特定操作
  • 可以根据需要选择继承哪个接口

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

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

原理说明

  • 面向接口编程,而非面向实现编程
  • 依赖注入是实现 DIP 的主要方式
  • 提高代码的灵活性和可测试性

代码示例

// ❌ 违反 DIP:高层模块依赖具体实现
public class UserService {
    // 直接依赖具体实现
    private MySQLUserRepository repository = new MySQLUserRepository();
    
    public User getUser(Long id) {
        return repository.findById(id);
    }
}

// ✅ 符合 DIP:依赖抽象
public interface UserRepository {
    User findById(Long id);
    void save(User user);
}

@Repository
public class MySQLUserRepository implements UserRepository {
    @Override
    public User findById(Long id) { }
    
    @Override
    public void save(User user) { }
}

@Service
public class UserService {
    private final UserRepository userRepository;
    
    // 通过构造器注入依赖
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public User getUser(Long id) {
        return userRepository.findById(id);
    }
}

// 测试时可以轻松替换实现
public class InMemoryUserRepository implements UserRepository {
    private Map<Long, User> users = new HashMap<>();
    
    @Override
    public User findById(Long id) {
        return users.get(id);
    }
    
    @Override
    public void save(User user) {
        users.put(user.getId(), user);
    }
}

框架中的应用

Spring Framework 核心

  • @Autowired 和构造函数注入
  • ApplicationContext 作为 IoC 容器
  • DataSource 接口,可以切换不同数据库

其他重要设计原则

DRY 原则 (Don’t Repeat Yourself)

不要重复你自己。

// ❌ 重复代码
public class OrderService {
    public void createOrder(Order order) {
        if (order.getPrice() < 0) {
            throw new IllegalArgumentException("价格不能为负数");
        }
        // 创建订单逻辑
    }
    
    public void updateOrder(Order order) {
        if (order.getPrice() < 0) {
            throw new IllegalArgumentException("价格不能为负数");
        }
        // 更新订单逻辑
    }
}

// ✅ 提取公共方法
public class OrderService {
    public void createOrder(Order order) {
        validateOrder(order);
        // 创建订单逻辑
    }
    
    public void updateOrder(Order order) {
        validateOrder(order);
        // 更新订单逻辑
    }
    
    private void validateOrder(Order order) {
        if (order.getPrice() < 0) {
            throw new IllegalArgumentException("价格不能为负数");
        }
    }
}

KISS 原则 (Keep It Simple, Stupid)

保持简单,愚蠢。

// ❌ 过度设计
public abstract class AbstractDataProcessor<T extends Data> 
    implements Processor<T>, Validator<T>, Converter<T> {
    // 复杂的继承体系
}

// ✅ 简单直接
public class UserDataProcessor {
    public User process(UserData data) {
        validate(data);
        return convert(data);
    }
    
    private void validate(UserData data) { }
    private User convert(UserData data) { }
}

YAGNI 原则 (You Ain’t Gonna Need It)

你不会需要它。

不要为未来可能的需求编写代码,只实现当前需要的功能。

// ❌ 过早优化,添加了目前不需要的功能
public class UserService {
    public User getUser(Long id) { }
    public User getUserByEmail(String email) { } // 目前不需要
    public User getUserByPhone(String phone) { } // 目前不需要
    public List<User> getUsersByRole(String role) { } // 目前不需要
    public void exportUsersToExcel() { } // 目前不需要
    public void importUsersFromExcel() { } // 目前不需要
}

// ✅ 只实现当前需要的
public class UserService {
    public User getUser(Long id) { }
    public void createUser(User user) { }
}

最小知识原则 (Law of Demeter)

一个对象应该对其他对象有最少的了解。

// ❌ 违反最小知识原则
public class OrderProcessor {
    public void process(Order order) {
        // 深入访问对象的内部结构
        User user = order.getUser();
        Address address = user.getAddress();
        String city = address.getCity();
        // ...
    }
}

// ✅ 符合最小知识原则
public class Order {
    public String getUserCity() {
        return user.getAddress().getCity();
    }
}

public class OrderProcessor {
    public void process(Order order) {
        String city = order.getUserCity();
        // ...
    }
}

原则之间的关系

原则核心目标实现方式
SRP职责单一类拆分
OCP扩展性抽象和多态
LSP继承正确性契约设计
ISP接口精简接口拆分
DIP解耦依赖注入
DRY避免重复提取公共代码
KISS简洁避免过度设计
YAGNI按需实现延迟实现

最佳实践

  1. 不要过度设计:先让代码工作,再考虑优化
  2. 逐步重构:持续改进代码质量
  3. 单元测试:确保重构不会破坏功能
  4. 代码审查:团队共同维护代码质量
  5. 学习框架源码:Spring、JDK 等都很好地应用了这些原则

总结

设计原则是编写高质量代码的指南,但不是绝对的规则。在实际开发中,需要根据具体情况灵活运用。理解这些原则背后的思想,比机械地遵守规则更重要。