Skip to content

设计模式 商业场景训练营

这页把设计模式放到真实商业项目里学习。目标不是背 UML,而是能判断什么时候该用策略、工厂、模板、责任链、观察者、代理、适配器;也能判断什么时候不要用,避免过度设计。

训练目标

学完这页,你要能做到:

  1. 解释设计模式的核心是隔离变化点,不是套模板。
  2. 用策略模式改造支付方式、折扣规则、医院接口差异。
  3. 用工厂模式统一创建不同业务处理器。
  4. 用模板方法固定导入流程,开放校验、转换、落库步骤。
  5. 用责任链组织订单校验、接口安全校验、采集数据校验。
  6. 用观察者模式做领域事件通知,但知道它和 MQ 的边界。
  7. 用代理模式理解 Spring AOP、事务、权限和日志增强。
  8. 用适配器模式屏蔽第三方医院接口差异。

商业场景总览

mermaid
flowchart TD
    A["商业系统变化点"] --> B["支付和折扣规则"]
    A --> C["采集数据导入流程"]
    A --> D["接口安全校验链"]
    A --> E["第三方医院接口差异"]
    A --> F["下单后通知多方"]
    B --> G["策略 + 工厂"]
    C --> H["模板方法 + 责任链"]
    D --> I["责任链"]
    E --> J["适配器 + 策略"]
    F --> K["观察者 / MQ"]

设计模式的判断顺序:

  1. 变化点是否真实存在。
  2. 变化是否会持续发生。
  3. 抽象后是否减少修改老代码。
  4. 新人是否能理解。
  5. 测试是否更容易。

场景一:支付方式选择

如果这样写:

java
public PayResult pay(PayCommand command) {
    if ("ALI".equals(command.channel())) {
        return aliPay(command);
    }
    if ("WECHAT".equals(command.channel())) {
        return wechatPay(command);
    }
    if ("BANK".equals(command.channel())) {
        return bankPay(command);
    }
    throw new IllegalArgumentException("不支持的支付方式");
}

问题:

  1. 新增支付方式要改老方法。
  2. 每个分支参数校验、签名、调用、异常处理混在一起。
  3. 单元测试困难。
  4. 分支越来越多后主流程变得臃肿。

策略接口

java
public interface PayStrategy {
    String channel();

    PayResult pay(PayCommand command);
}

支付策略

java
@Component
public class AliPayStrategy implements PayStrategy {
    @Override
    public String channel() {
        return "ALI";
    }

    @Override
    public PayResult pay(PayCommand command) {
        // 参数校验、签名、调用支付宝、解析响应
        return PayResult.success(command.orderNo());
    }
}

策略工厂

java
@Component
public class PayStrategyFactory {
    private final Map<String, PayStrategy> strategyMap;

    public PayStrategyFactory(List<PayStrategy> strategies) {
        this.strategyMap = strategies.stream()
                .collect(Collectors.toMap(PayStrategy::channel, Function.identity()));
    }

    public PayStrategy get(String channel) {
        PayStrategy strategy = strategyMap.get(channel);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的支付方式: " + channel);
        }
        return strategy;
    }
}

业务使用

java
public PayResult pay(PayCommand command) {
    PayStrategy strategy = payStrategyFactory.get(command.channel());
    return strategy.pay(command);
}

这不是为了“高级”,而是因为支付方式确实会变化。新增一种支付方式时,只新增一个策略类,主流程不改。

场景二:采集数据导入流程

采集导入通常流程固定:

  1. 读取原始数据。
  2. 校验格式。
  3. 转换字段。
  4. 去重。
  5. 批量入库。
  6. 发送资产变更事件。

不同医院、不同数据类型只是在某些步骤不同。适合模板方法。

java
public abstract class AbstractCollectImportTemplate<T> {

    public final ImportResult importData(ImportContext context) {
        List<String> rawRows = read(context);
        List<T> records = parse(rawRows);
        validate(records);
        List<T> cleaned = clean(records);
        saveBatch(cleaned);
        afterSaved(cleaned);
        return ImportResult.success(cleaned.size());
    }

    protected abstract List<String> read(ImportContext context);

    protected abstract List<T> parse(List<String> rawRows);

    protected void validate(List<T> records) {
    }

    protected List<T> clean(List<T> records) {
        return records;
    }

    protected abstract void saveBatch(List<T> records);

    protected void afterSaved(List<T> records) {
    }
}

好处:

  1. 主流程固定,不会每个导入类都写一遍。
  2. 变化步骤留给子类实现。
  3. 统一加日志、耗时、异常处理更容易。
  4. 不同数据源可以复用同一流程骨架。

代价:

  1. 父类流程变动会影响所有子类。
  2. 继承层次过深会难理解。
  3. 如果流程并不稳定,不适合过早抽模板。

场景三:接口安全校验链

开放接口校验通常有顺序:

mermaid
flowchart TD
    A["请求进入"] --> B["appId 校验"]
    B --> C["timestamp 校验"]
    C --> D["nonce 防重放"]
    D --> E["bodyHash 校验"]
    E --> F["signature 验签"]
    F --> G["业务权限校验"]

适合责任链。

java
public interface OpenApiCheckHandler {
    void check(OpenApiRequest request);
}
java
@Component
public class TimestampCheckHandler implements OpenApiCheckHandler {
    @Override
    public void check(OpenApiRequest request) {
        long diff = Math.abs(System.currentTimeMillis() - request.timestamp());
        if (diff > Duration.ofMinutes(5).toMillis()) {
            throw new SecurityException("请求已过期");
        }
    }
}
java
@Service
public class OpenApiCheckChain {
    private final List<OpenApiCheckHandler> handlers;

    public OpenApiCheckChain(List<OpenApiCheckHandler> handlers) {
        this.handlers = handlers;
    }

    public void check(OpenApiRequest request) {
        for (OpenApiCheckHandler handler : handlers) {
            handler.check(request);
        }
    }
}

为什么责任链合适:

  1. 每个校验点职责单一。
  2. 新增校验不改主流程。
  3. 可以控制顺序。
  4. 可以单独测试每个 Handler。

不适合的情况:只有一两个固定校验,直接写清楚就行。

场景四:第三方医院接口适配

不同医院接口协议不同:

医院入参出参鉴权
A 医院JSONJSONappKey 签名
B 医院XMLXMLtoken
C 医院文件CSVSFTP

业务系统希望统一调用:

java
public interface HospitalAdapter {
    String hospitalCode();

    List<PatientRecord> fetchPatients(FetchCommand command);
}
java
@Component
public class HospitalAAdapter implements HospitalAdapter {
    @Override
    public String hospitalCode() {
        return "HOSPITAL_A";
    }

    @Override
    public List<PatientRecord> fetchPatients(FetchCommand command) {
        // 把统一 FetchCommand 转成 A 医院 JSON 请求
        // 调用 A 医院接口
        // 把 A 医院响应转换成统一 PatientRecord
        return List.of();
    }
}

适配器的作用是屏蔽外部差异,让内部模型保持稳定。

如果不用适配器,医院差异会散落在业务代码里,后续新增医院会到处改。

场景五:下单成功后的通知

下单成功后可能要:

  1. 发积分。
  2. 发优惠券。
  3. 发站内信。
  4. 同步 ES。
  5. 记录审计。

如果都写在下单方法里,主流程会很长。可以用观察者或领域事件。

java
public record OrderCreatedEvent(String orderNo, Long userId) {
}
java
@Component
public class OrderAuditListener {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        // 记录审计日志
    }
}

注意边界:

方式适合
Spring 本地事件同进程、轻量扩展、失败影响可控
MQ 事件跨服务、异步最终一致、可重试

不要用本地观察者替代分布式消息。订单服务和积分服务是不同服务时,应使用 MQ 或本地消息表。

场景六:代理模式理解 Spring 事务

Spring 事务常见基于代理:

mermaid
flowchart TD
    A["调用 Service 方法"] --> B["事务代理"]
    B --> C["开启事务"]
    C --> D["执行目标方法"]
    D --> E{"是否异常"}
    E -- "否" --> F["提交事务"]
    E -- "是" --> G["回滚事务"]

为什么自调用会失效?因为 this.method() 没经过代理。

java
public void outer() {
    this.inner(); // 没经过代理,inner 上的 @Transactional 可能不生效
}

@Transactional
public void inner() {
}

这就是设计模式和框架原理的连接点:理解代理模式,才能理解 Spring AOP、事务、权限、日志增强为什么这样工作。

模式选择表

问题推荐模式不用会怎样用过头会怎样
多种业务规则切换策略if/else 膨胀一两个规则也拆很多类
创建对象有选择逻辑工厂new 到处散落简单对象也绕工厂
固定流程部分步骤变化模板方法流程复制粘贴继承层次太深
多个校验顺序执行责任链主方法臃肿链路分散难排查
接口不兼容适配器外部差异污染内部模型过度包装
一件事后通知多方观察者主流程耦合很多后置逻辑事件太多难追踪
横切增强代理日志、事务、权限重复写代理链太复杂

面试标准回答

设计模式怎么理解

text
设计模式不是为了套类图,而是为了隔离稳定变化点。比如多种算法或业务规则切换用策略,创建实现类变化用工厂,复杂对象构建用建造者,固定流程开放部分步骤用模板方法,多步校验用责任链,外部接口不兼容用适配器,横切增强用代理。使用时要避免过度设计,只有变化真实存在、抽象能减少修改面时才引入。

策略模式和工厂模式怎么配合

text
策略模式负责把不同算法或业务规则封装成独立实现,工厂模式负责根据类型找到对应策略。比如支付场景中,支付宝、微信、银行卡分别是不同 PayStrategy,PayStrategyFactory 根据 channel 返回对应策略,新增支付方式时只新增实现类,不改主支付流程。

责任链适合什么

text
责任链适合多个处理步骤按顺序执行,并且每个步骤职责相对独立的场景,例如开放接口验签链、订单创建校验链、采集数据校验链。它能让每个 Handler 单独测试和扩展,但如果链路太长、顺序不清晰,也会增加排查成本。

关联知识点