软件工程SOLID 五大设计原则

缩写 全称 中文 核心一句话
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。


生产落地小总结

  1. SRP:DDD领域实体 + 仓储分离就是SRP实践;
  2. OCP:策略模式做优惠、渠道、规则,是最常用落地;
  3. LSP:支付、库存子类实现最容易踩坑,重点关注异常、返回值契约;
  4. ISP:不要在一个接口堆一堆能力,按调用方去拆分接口;
  5. DIP:业务层依赖Repository接口,不直接依赖Mybatis/Mongo实现类。
相关推荐
涛涛ing5 小时前
2026年HTML迎来重磅进化:原生3D、可定制Select、声明式交互,前端开发范式正在被改写
后端
汉堡大王95275 小时前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
孙启超5 小时前
【AI开发之Rust】第 11 课:智能指针与内部可变性
开发语言·后端·rust
梦想很大很大5 小时前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
Achou.Wang6 小时前
k8s中nginx worker process自动设置
后端·golang
沙蒿同学6 小时前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端
Terra.K6 小时前
后端+AIAGENT项目开发指南
后端·agent·个人开发
拖孩6 小时前
代码我能全交给 AI,流量主这 500 个访客它一个都替不了我
前端·后端·微信小程序
烈风逍遥7 小时前
第七篇:提示词模板管理与 Agent 提示词编排
前端·人工智能·后端