| 缩写 | 全称 | 中文 | 核心一句话 |
|---|---|---|---|
| S | Single Responsibility Principle | 单一职责原则 | 一个类只负责一件事 |
| O | Open/Closed Principle | 开闭原则 | 对扩展开放,对修改关闭 |
| L | Liskov Substitution Principle | 里氏替换原则 | 子类可以完全替换父类,程序不出错 |
| I | Interface Segregation Principle | 接口隔离原则 | 不要强迫类实现它不需要的接口 |
| D | Dependency Inversion Principle | 依赖倒置原则 | 高层、低层模块都依赖抽象,不依赖具体实现 |
S 单一职责 SRP
定义:一个类只有一个变更理由
业务场景:订单实体不要同时做业务计算、持久化、发消息。
❌ 反面(违反SRP,新手常写)
java
// 坏例子:一个类干三件事
public class Order {
private Long id;
private BigDecimal amount;
// 1. 业务计算
public BigDecimal calcDiscount() { ... }
// 2. 数据库保存
public void saveToDb() { ... }
// 3. 发送MQ消息
public void sendCreateEvent() { ... }
}
问题:改优惠规则、改数据库字段、改MQ消息体,都要修改Order类,极易引入bug。
✅ 正确拆分
java
// 1. 领域实体:仅保存订单状态和基础属性(只负责订单自身数据)
public class Order {
private Long id;
private BigDecimal amount;
public BigDecimal calcDiscount(DiscountRule rule) { ... }
}
// 2. 仓储层:只负责数据库读写
public class OrderRepository {
public void save(Order order) { ... }
}
// 3. 事件发送:只负责消息投递
public class OrderEventPublisher {
public void publishOrderCreated(Order order) { ... }
}
变更隔离:改MQ只碰
OrderEventPublisher,改SQL只碰OrderRepository。
O 开闭原则 OCP
定义:对扩展开放,对修改关闭;新增优惠类型,不改动原有订单计算代码
业务:电商优惠,满减、优惠券、会员折扣;后续还要新增限时秒杀折扣。
❌ 反面:if-else 硬编码
java
public class DiscountService {
public BigDecimal calculate(Order order, String discountType){
if("FULL_REDUCE".equals(discountType)){
return fullReduce(order);
}else if("COUPON".equals(discountType)){
return coupon(order);
}
// 新增秒杀折扣 → 必须修改这个方法,违反OCP
return BigDecimal.ZERO;
}
}
✅ 正向:抽象优惠策略
java
// 抽象优惠接口
public interface DiscountStrategy {
BigDecimal compute(Order order);
}
// 已有实现:满减
public class FullReduceStrategy implements DiscountStrategy{}
// 已有实现:优惠券
public class CouponStrategy implements DiscountStrategy{}
// 计算服务,不需要修改,新增优惠只新增Strategy类
public class DiscountService {
private DiscountStrategy strategy;
public DiscountService(DiscountStrategy strategy){
this.strategy = strategy;
}
public BigDecimal getDiscount(Order order){
return strategy.compute(order);
}
}
新增秒杀折扣:新建SeckillDiscountStrategy,原有DiscountService完全不动。
L 里氏替换 LSP
定义:子类替换父类,业务行为不变,不破坏原有逻辑契约
业务场景:支付渠道。父类定义支付接口契约。
❌ 反面(经典业务坑,违反LSP)
java
public abstract class PaymentChannel {
// 契约:正常情况支付成功返回true;余额不足抛业务异常
public abstract boolean pay(Long orderId, BigDecimal money);
}
// 子类:信用支付(先赊账,不用扣钱)
public class CreditPayment extends PaymentChannel{
@Override
public boolean pay(Long orderId, BigDecimal money) {
// 破坏契约:无论金额多大都返回true,不做额度校验
// 原有上层代码以为已经扣款,实际只是记账,业务逻辑直接出错
return true;
}
}
问题:上层订单结算代码,把CreditPayment当作PaymentChannel传入,行为不符合父类约定,业务产生资金漏洞。
✅ 正确:子类严格遵守父类行为契约
java
public class CreditPayment extends PaymentChannel{
@Override
public boolean pay(Long orderId, BigDecimal money) {
// 遵守契约:额度不足抛出同样类型业务异常,上层代码可统一处理
if(creditLimit.compareTo(money) < 0){
throw new BizException("信用额度不足");
}
// 记账
return true;
}
}
替换后,上层订单结算逻辑完全不用改动,行为预期一致。
LSP核心:不光方法签名能继承,业务行为契约也要一致。
I 接口隔离 ISP
定义:客户端不依赖不需要的接口,胖接口拆成多个小接口
业务:订单的各种操作:创建订单、取消订单、退款、导出订单报表。
后台有两种客户端:普通下单服务、财务报表系统。
❌ 反面:大胖接口
java
// 胖接口,所有实现类被迫实现全部方法
public interface OrderOperator {
void createOrder(Order order);
void cancelOrder(Long orderId);
void refund(Long orderId);
void exportReport(); // 报表导出
}
// 下单服务实现这个接口,根本不需要exportReport,只能写空实现
public class OrderBizService implements OrderOperator{
@Override
public void exportReport() {
// 空方法,污染代码,容易被误调用
}
}
✅ 拆分细粒度接口
java
public interface OrderCrud {
void createOrder(Order order);
void cancelOrder(Long orderId);
void refund(Long orderId);
}
public interface OrderReport {
void exportReport();
}
// 业务服务只实现订单操作接口,不用管报表
public class OrderBizService implements OrderCrud{}
// 财务报表类单独实现报表接口
public class OrderReportService implements OrderReport{}
业务类只依赖自己需要的接口,不会被迫实现无关能力。
D 依赖倒置 DIP
高层业务不依赖低层具体实现,两者依赖抽象
业务:订单保存,底层可以切换MySQL / TiDB。
❌ 反面:高层直接new底层实现,硬耦合
java
// 高层业务服务,直接依赖MySQL具体实现
public class OrderService {
// 写死MySQL,换TiDB要修改OrderService代码
private MysqlOrderDao orderDao = new MysqlOrderDao();
public void createOrder(Order order){
orderDao.insert(order);
}
}
✅ 正向:依赖抽象仓储接口
java
// 抽象(高层、底层共同依赖抽象)
public interface OrderRepository {
void insert(Order order);
}
// 底层实现
public class MysqlOrderRepository implements OrderRepository{}
public class TidbOrderRepository implements OrderRepository{}
// 高层业务,只依赖抽象,不关心数据库类型(Spring DI注入)
public class OrderService {
private final OrderRepository repository;
// 注入抽象,而不是具体Mysql实现
public OrderService(OrderRepository repository){
this.repository = repository;
}
public void createOrder(Order order){
repository.insert(order);
}
}
切换数据库,只更换注入的Repository实现,OrderService一行不动。
Spring IOC核心思想就是落地DIP。
生产落地小总结
- SRP:DDD领域实体 + 仓储分离就是SRP实践;
- OCP:策略模式做优惠、渠道、规则,是最常用落地;
- LSP:支付、库存子类实现最容易踩坑,重点关注异常、返回值契约;
- ISP:不要在一个接口堆一堆能力,按调用方去拆分接口;
- DIP:业务层依赖Repository接口,不直接依赖Mybatis/Mongo实现类。