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:
ArrayList、LinkedList可以替换ListHashSet、TreeSet可以替换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 | 按需实现 | 延迟实现 |
最佳实践
- 不要过度设计:先让代码工作,再考虑优化
- 逐步重构:持续改进代码质量
- 单元测试:确保重构不会破坏功能
- 代码审查:团队共同维护代码质量
- 学习框架源码:Spring、JDK 等都很好地应用了这些原则
总结
设计原则是编写高质量代码的指南,但不是绝对的规则。在实际开发中,需要根据具体情况灵活运用。理解这些原则背后的思想,比机械地遵守规则更重要。