diff --git a/notes/设计模式 - 目录.md b/notes/设计模式 - 目录.md index d17d7ce5..d85cb816 100644 --- a/notes/设计模式 - 目录.md +++ b/notes/设计模式 - 目录.md @@ -16,6 +16,66 @@ JDK 中,Runtime.getRuntime() 是单例,Calendar.getInstance() 是静态工 Spring 中的用法见 [Spring 中的设计模式](Spring.md#七spring-中的设计模式)。 +后面的目录中,标为“选读”的模式在面试中很少单独问到,了解意图即可。 + +### 项目中的用法 + +设计模式针对的是变化:某处代码会随着需求不断增加分支或步骤时,用模式把变化的部分隔离出来,以后只需新增代码而不必修改已有的代码。以电商的下单流程为例,几个常用模式各自用在: + +| 环节 | 变化点 | 模式 | +| --- | --- | --- | +| 下单前的校验 | 校验项不断增加:参数、限购、黑名单、库存 | [责任链](设计模式%20-%20责任链.md) | +| 计算运费、选择支付渠道 | 同一件事有多种算法,按类型选择其中一种 | [策略](设计模式%20-%20策略.md)加[简单工厂](设计模式%20-%20简单工厂.md) | +| 接收多家物流公司的回调 | 步骤固定(验签、幂等、处理、应答),只有处理的部分不同 | [模板方法](设计模式%20-%20模板方法.md) | +| 支付成功后加积分、发短信 | 接收方不断增加,发布方不想知道有哪些接收方 | [观察者](设计模式%20-%20观察者.md) | +| 事务、分布式锁、日志 | 与业务无关、很多方法都要加的逻辑 | [代理](设计模式%20-%20代理.md),即 Spring AOP | + +以下单前的校验为例,每个校验项是一个处理者,按顺序执行,任何一个不通过就抛出异常,后面的不再执行: + +```java +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 validators; // Spring 按 @Order 排好序注入 + + public OrderService(List validators) { + this.validators = validators; + } + + public void create(OrderRequest req) { + validators.forEach(v -> v.check(req)); + // 校验全部通过,创建订单 + } +} +``` + +再加上库存校验(@Order(3)),购买 3 件时依次执行参数校验、限购校验,抛出“每人限购 2 件”,库存校验不再执行(Spring Boot 3.3 实测,见[附录 A1](#a1-下单校验的责任链))。新增黑名单校验只需要再写一个带 @Component 的实现类,OrderService 不用修改。 + +几个容易混淆的模式: + +- 策略和工厂常一起出现:工厂决定取得哪一个对象,策略决定执行哪一种算法,见[与简单工厂和 Spring 容器结合](设计模式%20-%20策略.md#2-与简单工厂和-spring-容器结合); +- 工厂方法一次创建一个对象,由子类决定创建哪一种;抽象工厂创建一组必须一起使用的相关对象,见[抽象工厂](设计模式%20-%20抽象工厂.md); +- 责任链中的请求依次经过多个处理者,策略只选择其中一个算法执行。 + +需要注意的是,模式不是用得越多越好。只有两三个分支、短期内不会再增加的 if-else 不需要改成策略,引入模式的理由应该是已经出现或可以预见的变化。 + ## 二、创建型 - [单例.md](设计模式%20%20-%20单例.md) @@ -23,33 +83,51 @@ Spring 中的用法见 [Spring 中的设计模式](Spring.md#七spring-中的设 - [工厂方法.md](设计模式%20-%20工厂方法.md) - [抽象工厂.md](设计模式%20-%20抽象工厂.md) - [生成器.md](设计模式%20-%20生成器.md) -- [原型模式.md](设计模式%20-%20原型模式.md) +- [原型模式.md](设计模式%20-%20原型模式.md)(选读) ## 三、行为型 - [责任链.md](设计模式%20-%20责任链.md) -- [命令.md](设计模式%20-%20命令.md) -- [解释器.md](设计模式%20-%20解释器.md) -- [迭代器.md](设计模式%20-%20迭代器.md) -- [中介者.md](设计模式%20-%20中介者.md) -- [备忘录.md](设计模式%20-%20备忘录.md) +- [命令.md](设计模式%20-%20命令.md)(选读) +- [解释器.md](设计模式%20-%20解释器.md)(选读) +- [迭代器.md](设计模式%20-%20迭代器.md)(选读) +- [中介者.md](设计模式%20-%20中介者.md)(选读) +- [备忘录.md](设计模式%20-%20备忘录.md)(选读) - [观察者.md](设计模式%20-%20观察者.md) -- [状态.md](设计模式%20-%20状态.md) +- [状态.md](设计模式%20-%20状态.md)(选读) - [策略.md](设计模式%20-%20策略.md) - [模板方法.md](设计模式%20-%20模板方法.md) -- [访问者.md](设计模式%20-%20访问者.md) -- [空对象.md](设计模式%20-%20空对象.md) +- [访问者.md](设计模式%20-%20访问者.md)(选读) +- [空对象.md](设计模式%20-%20空对象.md)(选读) ## 四、结构型 - [适配器.md](设计模式%20-%20适配器.md) -- [桥接.md](设计模式%20-%20桥接.md) -- [组合.md](设计模式%20-%20组合.md) +- [桥接.md](设计模式%20-%20桥接.md)(选读) +- [组合.md](设计模式%20-%20组合.md)(选读) - [装饰.md](设计模式%20-%20装饰.md) -- [外观.md](设计模式%20-%20外观.md) -- [享元.md](设计模式%20-%20享元.md) +- [外观.md](设计模式%20-%20外观.md)(选读) +- [享元.md](设计模式%20-%20享元.md)(选读) - [代理.md](设计模式%20-%20代理.md) +## 附录:实测 + +### 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(): + +```text +== 购买 1 件 +参数校验 +限购校验 +库存校验 +创建订单 +== 购买 3 件 +参数校验 +限购校验 +下单失败:每人限购 2 件 +``` + ## 参考资料 - 弗里曼. Head First 设计模式 [M]. 中国电力出版社, 2007. @@ -58,3 +136,4 @@ Spring 中的用法见 [Spring 中的设计模式](Spring.md#七spring-中的设 - [Design Patterns](http://www.oodesign.com/) - [Design patterns implemented in Java](http://java-design-patterns.com/) - [The breakdown of design patterns in JDK](http://www.programering.com/a/MTNxAzMwATY.html) +- [Spring Framework: Using @Autowired](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config/autowired.html)