CS-Notes全部笔记在阅读器中打开

设计模式目录

一、前言

设计模式是针对反复出现的设计问题给出的解决方案。它关心类和对象之间如何划分职责、如何协作,使需求变化时只需修改少量代码;它描述的是解决思路,而不是可以直接复制的代码。

学习设计模式,一方面可以复用前人总结的设计经验,另一方面在讨论设计时可以用“策略”“装饰”等名词代替大段的描述。

四人组(Gang of Four, GoF)在《设计模式》中把 23 种模式分为三类:

JDK 中,Runtime.getRuntime() 是单例,Calendar.getInstance() 是静态工厂方法,BufferedInputStream 包装其它输入流以增加缓冲,属于装饰。Arrays.asList() 把数组适配为 List,属于适配器;java.lang.reflect.Proxy 在运行时生成代理对象。

集合框架中,List.sort() 传入的 Comparator 是策略,换一个 Comparator 就换一种排序规则;继承 AbstractList 只需实现 get() 和 size(),indexOf()、contains() 等方法由父类基于这两个方法实现,属于模板方法;Collection#iterator() 由各实现类决定返回哪种迭代器,属于工厂方法。

Spring 中的用法见 Spring 中的设计模式。

后面的目录中,标为“选读”的模式在面试中很少单独问到,了解意图即可。

项目中的用法

设计模式针对的是变化:某处代码会随着需求不断增加分支或步骤时,用模式把变化的部分隔离出来,以后只需新增代码而不必修改已有的代码。以电商的下单流程为例,几个常用模式各自用在:

环节 变化点 模式
下单前的校验 校验项不断增加:参数、限购、黑名单、库存 责任链
计算运费、选择支付渠道 同一件事有多种算法,按类型选择其中一种 策略加简单工厂
接收多家物流公司的回调 步骤固定(验签、幂等、处理、应答),只有处理的部分不同 模板方法
支付成功后加积分、发短信 接收方不断增加,发布方不想知道有哪些接收方 观察者,即 Spring 的事件
事务、分布式锁、日志 与业务无关、很多方法都要加的逻辑 代理,即 Spring AOP

以下单前的校验为例,每个校验项是一个处理者,按顺序执行,任何一个不通过就抛出异常,后面的不再执行:

public interface OrderValidator {
    void check(OrderRequest req);          // 不通过时抛出异常,链条到此为止
}

@Component @Order(1)
public class ParamValidator implements OrderValidator {
    public void check(OrderRequest req) {
        if (req.count() <= 0) throw new IllegalArgumentException("数量必须大于 0");
    }
}

@Component @Order(2)
public class LimitValidator implements OrderValidator {
    public void check(OrderRequest req) {
        if (req.count() > 2) throw new IllegalStateException("每人限购 2 件");
    }
}

@Service
public class OrderService {
    private final List<OrderValidator> validators;     // Spring 按 @Order 排好序注入

    public OrderService(List<OrderValidator> validators) {
        this.validators = validators;
    }

    public void create(OrderRequest req) {
        validators.forEach(v -> v.check(req));
        // 校验全部通过,创建订单
    }
}

再加上库存校验(@Order(3)),购买 3 件时依次执行参数校验、限购校验,抛出“每人限购 2 件”,库存校验不再执行(Spring Boot 3.3 实测,见附录 A1)。新增黑名单校验只需要再写一个带 @Component 的实现类,OrderService 不用修改。

需要注意的是,链上的处理者最好只做校验这类不修改数据的事。如果某个处理者已经扣了优惠券,后面的处理者再失败,前面的修改不会自动撤销,需要把整条链放在一个事务中,或者由处理者提供补偿操作。链条变长以后,请求在哪一环被拦下也要靠日志才能查清。

几个容易混淆的模式:

需要注意的是,模式不是用得越多越好。只有两三个分支、短期内不会再增加的 if-else 不需要改成策略,引入模式的理由应该是已经出现或可以预见的变化。

二、创建型

三、行为型

四、结构型

附录:实测

A1 下单校验的责任链

Spring Boot 3.3.5(Spring Framework 6.1.14)、JDK 21,Apple M1 Pro。三个校验器与正文相同,库存校验 StockValidator 标注 @Order(3)、只打印一行;每个校验器先打印自己的名称。用 @Import 注册时故意按库存、参数、限购的顺序列出,注入的 List 仍按 @Order 排序。依次以购买 1 件和 3 件调用 create():

== 购买 1 件
参数校验
限购校验
库存校验
创建订单
== 购买 3 件
参数校验
限购校验
下单失败:每人限购 2 件

参考资料

在阅读器中可以用目录和搜索、切换深色模式、练习这篇笔记中的面试题:打开《设计模式目录》