一,行为模式介绍
1.1 行为型模式的核心思想
结构型模式解决的是“类或对象如何组成更大的结构”,而行为型模式解决的是 **“对象之间如何交互与协作 **”。
通俗地讲:行为型模式让系统的行为更灵活、更易扩展,而不需要硬编码地绑定对象之间的关系。
1.2 常见的 11 种行为型模式
| 模式名称 | 主要作用 | 简要说明 |
|---|---|---|
| 1. 策略模式(Strategy) | 封装算法 | 允许在运行时自由切换算法,例如不同的排序策略 |
| 2. 模板方法模式(Template Method) | 固定流程,延迟实现 | 抽象出算法骨架,部分步骤由子类实现 |
| 3. 观察者模式(Observer) | 发布-订阅 | 一处状态变化,自动通知所有观察者 |
| 4. 责任链模式(Chain of Responsibility) | 职责传递 | 多个对象按顺序处理请求,可中断或继续传递 |
| 5. 命令模式(Command) | 封装请求 | 把请求封装为对象,可撤销、记录、队列化 |
| 6. 状态模式(State) | 状态驱动行为 | 对象根据状态不同,行为不同 |
| 7. 迭代器模式(Iterator) | 顺序访问集合 | 提供统一方式遍历集合,而不暴露内部结构 |
| 8. 中介者模式(Mediator) | 简化多对象通信 | 用一个中介封装对象间复杂的交互 |
| 9. 备忘录模式(Memento) | 保存状态 | 支持撤销或回滚操作 |
| 10. 解释器模式(Interpreter) | 解释语法规则 | 用类表示语言语法,实现简单的解释器 |
| 11. 访问者模式(Visitor) | 操作分离 | 在不修改对象结构的情况下定义新操作 |
二,策略模式
2.1 什么是策略模式
定义:策略模式定义了一系列算法(或行为),并将每个算法封装起来,使它们可以互相替换,从而让算法的变化不会影响使用算法的代码。
通俗理解:当你的程序里出现大量的 if-else 或 switch 来区分不同的行为时,就可以用策略模式来代替它。
2.2 结构组成(3个角色)
| 角色 | 名称 | 作用 |
|---|---|---|
| Context | 上下文类 | 负责持有策略对象,并在运行时调用策略接口 |
| Strategy | 策略接口 | 定义算法的公共接口 |
| ConcreteStrategy | 具体策略类 | 实现具体的算法逻辑 |
2.3 UML 结构图
┌────────────────────┐
│ Strategy │
│ + execute():void │
└─────────┬──────────┘
│
┌──────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
ConcreteStrategyA ConcreteStrategyB ConcreteStrategyC
│ + execute() │ + execute() │ + execute()
└──────────────────┘ └─────────────────┘ └──────────────┘
┌────────────────────┐
│ Context │
│ - strategy:Strategy│
│ + setStrategy() │
│ + execute() │
└────────────────────┘
2.4 Java 实现示例
假设我们要实现一个“支付系统”,支持三种支付方式:支付宝、微信、银行卡。
传统写法可能是:
if (type.equals("ali")) { payByAli(); }
else if (type.equals("wechat")) { payByWechat(); }
else if (type.equals("bank")) { payByBank(); }
现在我们改用策略模式
策略接口(定义行为)
public interface PaymentStrategy {
void pay(double amount);
}
具体策略实现(不同支付方式)
public class AliPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用支付宝支付 " + amount + " 元");
}
}
public class WeChatPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用微信支付 " + amount + " 元");
}
}
public class BankPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用银行卡支付 " + amount + " 元");
}
}
上下文类(运行时切换策略)
public class PaymentContext {
private PaymentStrategy strategy;
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void executePayment(double amount) {
if (strategy == null) {
throw new IllegalStateException("未设置支付策略");
}
strategy.pay(amount);
}
}
测试使用
public class StrategyPatternDemo {
public static void main(String[] args) {
PaymentContext context = new PaymentContext();
context.setStrategy(new AliPayStrategy());
context.executePayment(100);
context.setStrategy(new WeChatPayStrategy());
context.executePayment(200);
context.setStrategy(new BankPayStrategy());
context.executePayment(300);
}
}
输出:
使用支付宝支付 100 元
使用微信支付 200 元
使用银行卡支付 300 元
2.5 应用场景
| 场景 | 应用说明 |
|---|---|
| 支付方式选择 | 支付宝、微信、银联支付 |
| 压缩算法 | zip、rar、tar 等不同压缩策略 |
| 登录方式 | 用户名密码、短信验证码、第三方授权登录 |
| 排序算法 | 快排、归并、冒泡等可互换策略 |
| 日志输出 | 输出到文件、数据库、控制台等策略 |
2.6 优缺点总结
优点:
- 避免大量的
if-else判断 - 算法可以自由替换、扩展
- 符合开闭原则(对扩展开放,对修改关闭)
缺点:
- 会增加类的数量(每个策略都是一个类)
- 客户端需要了解不同策略的区别
2.7 在实际项目中用法
2.7.1 策略模式+Spring bean自动注入
-
定义策略接口
public interface PromotionStrategy { String getType(); // 每个策略要能标识自己的类型 void execute(); // 执行逻辑 } -
编写具体策略实现类
import org.springframework.stereotype.Component; // 这个是重点,把具体的策略实现类变成bean @Component public class DiscountStrategy implements PromotionStrategy { @Override public String getType() { return "discount"; } @Override public void execute() { System.out.println("执行【打折】优惠策略"); } } @Component public class CashbackStrategy implements PromotionStrategy { @Override public String getType() { return "cashback"; } @Override public void execute() { System.out.println("执行【返现】优惠策略"); } } @Component public class FullReductionStrategy implements PromotionStrategy { @Override public String getType() { return "fullReduction"; } @Override public void execute() { System.out.println("执行【满减】优惠策略"); } }这些类会自动注册为 Spring 容器中的 Bean,Spring 启动时会自动扫描并实例化它们。
-
上下文类(自动收集所有策略)
import org.springframework.stereotype.Service; import java.util.Map; @Service public class PromotionStrategyContext { private final Map<String, PromotionStrategy> strategyMap; public PromotionStrategyContext(Map<String, PromotionStrategy> strategyMap) { this.strategyMap = strategyMap; } public void executeStrategy(String type) { // 通过 bean 名称匹配策略 PromotionStrategy strategy = strategyMap.get(type + "Strategy"); // Bean 名默认是类名首字母小写 if (strategy == null) { throw new IllegalArgumentException("无效的优惠类型: " + type); } strategy.execute(); } }Spring 会自动把容器中所有实现了
PromotionStrategy接口的 Bean;注入到一个Map<String, PromotionStrategy>中,其中key是 Bean 名(默认类名首字母小写)。 -
Controller 层调用
```java
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/promotion")
public class PromotionController {
private final PromotionStrategyContext context;
public PromotionController(PromotionStrategyContext context) {
this.context = context;
}
@GetMapping("/execute")
public String execute(@RequestParam String type) {
context.executeStrategy(type);
return "执行成功";
}
}
```
5. 测试调用
```url
GET /promotion/execute?type=discount
GET /promotion/execute?type=cashback
GET /promotion/execute?type=fullReduction
```
**输出**:
```
执行【打折】优惠策略
执行【返现】优惠策略
执行【满减】优惠策略
```
2.7.2 Spring 通用策略注册框架模板
-
项目结构
com.example.strategy │ ├── annotation │ └── StrategyType.java // 自定义注解 │ ├── core │ ├── Strategy.java // 策略接口 │ ├── StrategyFactory.java // 策略注册工厂 │ └── StrategyExecutor.java // 统一执行器 │ ├── impl │ ├── DiscountStrategy.java // 折扣策略 │ ├── CashbackStrategy.java // 返现策略 │ └── FullReductionStrategy.java // 满减策略 │ └── controller └── StrategyController.java // 测试入口 -
定义注解(标识策略类型)
package com.example.strategy.annotation; import java.lang.annotation.*; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface StrategyType { String value(); // 策略类型唯一标识 } -
策略接口
package com.example.strategy.core; public interface Strategy { /** * 执行策略逻辑 */ void execute(); } -
具体策略实现类
package com.example.strategy.impl; import com.example.strategy.annotation.StrategyType; import com.example.strategy.core.Strategy; import org.springframework.stereotype.Component; @StrategyType("discount") @Component public class DiscountStrategy implements Strategy { @Override public void execute() { System.out.println("执行【打折】策略"); } } @StrategyType("cashback") @Component public class CashbackStrategy implements Strategy { @Override public void execute() { System.out.println("执行【返现】策略"); } } @StrategyType("fullReduction") @Component public class FullReductionStrategy implements Strategy { @Override public void execute() { System.out.println("执行【满减】策略"); } } -
核心:策略注册工厂(自动扫描 + 注册)
package com.example.strategy.core; import com.example.strategy.annotation.StrategyType; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; @Component public class StrategyFactory implements ApplicationContextAware { private static final Map<String, Strategy> STRATEGY_MAP = new HashMap<>(); @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // 获取所有带 @StrategyType 注解的 Bean Map<String, Object> beans = applicationContext.getBeansWithAnnotation(StrategyType.class); beans.forEach((beanName, bean) -> { StrategyType annotation = bean.getClass().getAnnotation(StrategyType.class); STRATEGY_MAP.put(annotation.value(), (Strategy) bean); System.out.println("注册策略: " + annotation.value() + " -> " + bean.getClass().getSimpleName()); }); } public static Strategy getStrategy(String type) { Strategy strategy = STRATEGY_MAP.get(type); if (strategy == null) { throw new IllegalArgumentException("未找到策略类型: " + type); } return strategy; } }Spring 启动时,
StrategyFactory会自动扫描所有带@StrategyType的 Bean 并注册到内存 Map 中。 -
策略执行器(统一入口)
package com.example.strategy.core; import org.springframework.stereotype.Service; @Service public class StrategyExecutor { public void execute(String type) { Strategy strategy = StrategyFactory.getStrategy(type); strategy.execute(); } } -
控制器测试
package com.example.strategy.controller; import com.example.strategy.core.StrategyExecutor; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/strategy") public class StrategyController { private final StrategyExecutor executor; public StrategyController(StrategyExecutor executor) { this.executor = executor; } @GetMapping("/execute") public String execute(@RequestParam String type) { executor.execute(type); return "执行成功:" + type; } }
三,模板方法模式
3.1 模板方法模式的核心思想
定义一个算法的骨架(流程模板),将一些步骤延迟到子类中实现,让子类在不改变算法整体结构的情况下,重新定义某些特定步骤。
换句话说:抽象类定义流程骨架,子类去决定具体实现。
- 抽象类中规定了**“流程步骤**”
- 子类中去实现“细节逻辑”
3.2 结构
| 概念 | 说明 |
|---|---|
| 模板方法(Template Method) | 定义算法骨架,不允许子类修改整体流程 |
| 抽象方法(Abstract Method) | 延迟到子类去实现的步骤 |
| 钩子方法(Hook Method) | 子类可选择性重写的“可选步骤” |
| final | 常用于模板方法上,防止子类更改流程结构 |
| 通用方法(private Method) | 常用于模板里面通用的方法 |
假设我们有一个“泡茶和冲咖啡”的流程:
- 烧水
- 冲泡饮料(茶叶 or 咖啡粉)
- 倒入杯中
- 加调料(柠檬 or 牛奶)
虽然都是“制作饮料”,但步骤整体是固定的,差别在于第 2、4 步,这就非常适合模板方法模式。
3.3 代码示例
抽象模板类
public abstract class Beverage {
// 模板方法 — 定义制作流程
public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
// 固定的公共步骤
private void boilWater() {
System.out.println("Boiling water");
}
private void pourInCup() {
System.out.println("Pouring into cup");
}
// 抽象方法(交给子类实现)
protected abstract void brew();
protected abstract void addCondiments();
}
具体子类实现
public class Tea extends Beverage {
@Override
protected void brew() {
System.out.println("Steeping the tea");
}
@Override
protected void addCondiments() {
System.out.println("Adding lemon");
}
}
public class Coffee extends Beverage {
@Override
protected void brew() {
System.out.println("Dripping coffee through filter");
}
@Override
protected void addCondiments() {
System.out.println("Adding sugar and milk");
}
}
测试类
public class Main {
public static void main(String[] args) {
Beverage tea = new Tea();
tea.prepareRecipe();
System.out.println("-----");
Beverage coffee = new Coffee();
coffee.prepareRecipe();
}
}
输出结果
Boiling water
Steeping the tea
Pouring into cup
Adding lemon
-----
Boiling water
Dripping coffee through filter
Pouring into cup
Adding sugar and milk
3.4 加“钩子方法”的例子
钩子方法就是:在模板中预留一个“可选的扩展点”,让子类可以选择性地介入流程。
简单来说:
- 抽象方法(abstract)是“必须实现的步骤”;
- 钩子方法(hook)是“想改就改,不改用默认逻辑”的步骤。
代码实现:
-
抽象模板类
public abstract class Beverage { // 模板方法 — 固定流程 public final void prepareRecipe() { boilWater(); brew(); pourInCup(); if (customerWantsCondiments()) { // 这里就是“钩子点” addCondiments(); } } private void boilWater() { System.out.println("Boiling water"); } private void pourInCup() { System.out.println("Pouring into cup"); } protected abstract void brew(); protected abstract void addCondiments(); // 钩子方法:提供默认行为 protected boolean customerWantsCondiments() { return true; } } -
具体字类实现
public class Coffee extends Beverage { @Override protected void brew() { System.out.println("Dripping coffee through filter"); } @Override protected void addCondiments() { System.out.println("Adding sugar and milk"); } // 通过钩子方法控制是否执行 addCondiments() @Override protected boolean customerWantsCondiments() { return false; } }
这样子类可以灵活决定是否执行某一步骤。
3.5 模板方法模式常见应用场景
模板方法模式主要用在这种情况:
有一套“固定的业务流程”,但流程中的某些步骤在不同场景下略有不同。
抽象类把“流程骨架”固定好,子类只需要定义具体细节。
3.5.1 常见的 3 个实际场景
| 场景 | 说明 | 举例 |
|---|---|---|
| 数据访问类(数据库模板) | 操作数据库时,连接、执行、关闭等步骤固定,SQL不同 | Spring 的 JdbcTemplate |
| 报表生成流程 | 报表导出步骤固定,不同报表生成逻辑不同 | PDF导出、Excel导出 |
| 游戏/任务流程 | 游戏流程相同,不同关卡或Boss逻辑不同 | 统一框架调用不同实现 |
3.5.2 数据库访问模板举例
假设你有一个数据库操作流程:
- 连接数据库
- 执行SQL
- 处理结果
- 关闭连接
这些步骤的顺序固定,只是执行的 SQL 不同, 我们就可以用模板方法模式
// 抽象模板类
public abstract class DatabaseTemplate {
// 模板方法:固定执行流程
public final void execute() {
connect();
executeSQL();
close();
}
private void connect() {
System.out.println("Connecting to database...");
}
private void close() {
System.out.println("Closing connection...");
}
// 子类实现不同SQL逻辑
protected abstract void executeSQL();
}
子类只负责实现不同 SQL 逻辑
public class UserQuery extends DatabaseTemplate {
@Override
protected void executeSQL() {
System.out.println("Executing: SELECT * FROM users;");
}
}
public class OrderInsert extends DatabaseTemplate {
@Override
protected void executeSQL() {
System.out.println("Executing: INSERT INTO orders VALUES(...);");
}
}
测试
public class Main {
public static void main(String[] args) {
DatabaseTemplate query = new UserQuery();
query.execute();
System.out.println("----");
DatabaseTemplate insert = new OrderInsert();
insert.execute();
}
}
输出:
Connecting to database...
Executing: SELECT * FROM users;
Closing connection...
----
Connecting to database...
Executing: INSERT INTO orders VALUES(...);
Closing connection...
3.5.3 报表生成模板举例
报表导出时流程常见为:
- 获取数据
- 生成文件内容
- 保存文件
模板如下
public abstract class ReportExporter {
// 导出流程
public final void export() {
fetchData();
generateContent();
saveToFile();
}
//获取数据
protected abstract void fetchData();
//生成内容
protected abstract void generateContent();
//保存文件
private void saveToFile() {
System.out.println("Saving file to disk...");
}
}
子类实现不同导出逻辑:
// pdf导出
public class PdfExporter extends ReportExporter {
@Override
protected void fetchData() {
System.out.println("Fetching data from database...");
}
@Override
protected void generateContent() {
System.out.println("Generating PDF content...");
}
}
// excel导出
public class ExcelExporter extends ReportExporter {
@Override
protected void fetchData() {
System.out.println("Fetching data from API...");
}
@Override
protected void generateContent() {
System.out.println("Generating Excel content...");
}
}
3.6 优缺点
优点:
- 代码复用高,减少重复逻辑
- 控制流程的统一性(抽象层定义规范)
- 扩展性强(子类灵活实现)
缺点:
- 子类数目可能增多
- 调试时要理解整体调用流程
四,观察者模式
4.1 观察者模式的核心思想
定义对象间的一对多依赖关系,当一个对象(被观察者)状态发生变化时,所有依赖它的对象(观察者)都会收到通知并自动更新。
简单说就是:“一个人打喷嚏,所有人都知道了。”
类比理解
- 你关注了一个公众号(公众号是被观察者)
- 当公众号发布新文章时,它会自动通知所有粉丝(观察者)
这就是典型的观察者模式。
观察者模式让对象之间的依赖变成自动通知,是实现“事件驱动”和“解耦通信”的基础。
4.2 模式结构
| 角色 | 说明 |
|---|---|
| Subject(主题 / 被观察者) | 维护一组观察者,状态变化时通知它们 |
| Observer(观察者) | 接收通知并作出响应 |
| ConcreteSubject(具体主题) | 实际业务中状态变化的对象 |
| ConcreteObserver(具体观察者) | 实际接收并处理通知的对象 |
4.3 代码示例
我们模拟一个“气象站”系统,气象站发布天气变化 → 所有显示设备都自动更新。
观察者接口
public interface Observer {
void update(String weather);
}
被观察者(主题)接口(核心)
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
具体主题:气象站(核心)
import java.util.ArrayList;
import java.util.List;
public class WeatherStation implements Subject {
//存储所有需要通知的观察者
private List<Observer> observers = new ArrayList<>();
private String weather;
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
//核心逻辑:通知观察者
@Override
public void notifyObservers() {
for (Observer o : observers) {
o.update(weather);
}
}
// 当天气变化时自动通知所有观察者
public void setWeather(String weather) {
this.weather = weather;
System.out.println("Weather changed to: " + weather);
notifyObservers();
}
}
具体观察者:手机 / 屏幕显示设备
public class PhoneDisplay implements Observer {
private String name;
public PhoneDisplay(String name) {
this.name = name;
}
@Override
public void update(String weather) {
System.out.println(name + " received weather update: " + weather);
}
}
测试代码
public class Main {
public static void main(String[] args) {
WeatherStation station = new WeatherStation();
Observer phone1 = new PhoneDisplay("Alice's Phone");
Observer phone2 = new PhoneDisplay("Bob's Phone");
station.registerObserver(phone1);
station.registerObserver(phone2);
station.setWeather("Sunny");
System.out.println("-----");
station.setWeather("Rainy");
}
}
输出:
Weather changed to: Sunny
Alice's Phone received weather update: Sunny
Bob's Phone received weather update: Sunny
-----
Weather changed to: Rainy
Alice's Phone received weather update: Rainy
Bob's Phone received weather update: Rainy
4.4 观察者模式的常见应用场景
| 场景 | 示例 |
|---|---|
| GUI 事件监听 | 按钮点击 → 触发监听器回调 |
| 消息订阅 / 发布系统 | Kafka、MQ、Redis 发布订阅 |
| MVC 模式 | View 观察 Model 的变化 |
| 游戏事件系统 | 玩家状态变化通知界面刷新 |
| 日志框架 | 多个日志输出通道(控制台、文件) |
4.5 观察者模式在 Java 中的两种实现方式
-
手写结构实现:灵活、清晰,可自定义逻辑。
-
使用 JDK 自带的实:
Java 早期提供了:
java.util.Observable java.util.Observer但由于设计缺陷(如继承限制、线程安全问题),从 Java 9 开始就被标记为 deprecated,实际开发更推荐自己定义接口或使用事件总线(如 Guava EventBus)。
4.6 观察者模式的优缺点
优点:
- 解耦:主题与观察者之间松散依赖
- 扩展性好:随时添加/移除观察者
- 灵活:易于构建事件系统
缺点:
- 通知链复杂时可能难以追踪
- 可能引起性能问题(大量观察者)
- 通知机制若不慎,容易触发循环更新
4.7 事件驱动
事件驱动(Event-Driven) 是一种程序设计思想: “程序的执行,不是按顺序进行,而是由事件触发的。”也就是说,你不再主动去调用某个逻辑,而是 等待事件发生 时,让系统自动触发响应逻辑。
你可以想象一下按钮点击:
button.addActionListener(e -> System.out.println("按钮被点击"));
你不会在循环里不断判断按钮有没有被按下。
而是当 **“点击事件 **”发生时,系统自动通知你注册的监听器(观察者)去处理。
这就是典型的事件驱动模型:
- 事件源(button)相当于被观察者;
- **监听器(Listener)**相当于观察者;
- **事件(Event)**相当于传递的上下文信息。
观察者模式与事件驱动的关系
| 角色 | 观察者模式 | 事件驱动模型 |
|---|---|---|
| 被观察者 | Subject(主题) | 事件源(Event Source) |
| 观察者 | Observer | 事件监听器(Listener) |
| 通知机制 | 调用 update() | 发布事件(publish event) |
| 数据 | 状态或消息 | Event 对象(包含详细信息) |
结论:事件驱动其实就是“观察者模式的进化版” 它用事件对象来传递上下文信息,用统一的事件分发机制实现“松耦合通信”。
4.8 在 Spring 业务中使用观察者模式(事件驱动)
Spring 自身就内置了一个事件机制(基于观察者模式),叫 ApplicationEvent & ApplicationListener。
定义一个事件
import org.springframework.context.ApplicationEvent;
public class OrderCreatedEvent extends ApplicationEvent {
private final String orderId;
public OrderCreatedEvent(Object source, String orderId) {
super(source);
this.orderId = orderId;
}
public String getOrderId() {
return orderId;
}
}
定义事件监听者(观察者)
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class NotifyUserListener {
//这里的 @EventListener`就是告诉 Spring: “当某个事件发生时,调用这个方法。”
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
System.out.println("发送通知:订单创建成功 -> " + event.getOrderId());
}
}
发布事件(被观察者触发)
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final ApplicationEventPublisher publisher;
public OrderService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void createOrder(String orderId) {
System.out.println("创建订单:" + orderId);
// 发布事件 -> 自动触发所有监听这个事件的观察者
publisher.publishEvent(new OrderCreatedEvent(this, orderId));
}
}
执行结果
当你调用:
orderService.createOrder("ORD123");
输出:
创建订单:ORD123
发送通知:订单创建成功 -> ORD123
五,责任链模式
5.1 什么是责任链模式
核心思想:它将多个处理对象(Handler)串联成一条链,让请求在这条链上传递,直到有对象处理它为止。
简单理解就是:“我把请求交给第一个人,他要是处理不了,就转给下一个人,直到有人能处理。”
举个生活中的例子
比如你在公司请假:
- 请假 1 天 → 直接主管审批
- 请假 3 天 → 部门经理审批
- 请假 7 天 → 总经理审批
这就是一个典型的 责任链:
员工提交请假 → 主管 → 经理 → 总经理
每个人都可以决定是否处理请求,或者交给下一个人。
5.2 模式结构
核心角色有 3 个:
| 角色 | 名称 | 职责 |
|---|---|---|
| Handler | 抽象处理者 | 定义处理请求的接口,保存下一个处理者 |
| ConcreteHandler | 具体处理者 | 实现具体的处理逻辑 |
| Client | 请求发起者 | 构建责任链并发出请求 |
类图示意:
Client
|
v
Handler1 --> Handler2 --> Handler3
5.3 代码实现
我们用刚才的「请假审批」来写代码:
定义抽象处理者
public abstract class Handler {
// 下一个处理者
protected Handler next;
// 设置下一个处理者
public void setNext(Handler next) {
this.next = next;
}
// 处理请求(抽象方法)
public abstract void handleRequest(int leaveDays);
}
定义具体处理者们
// 主管审批
public class LeaderHandler extends Handler {
@Override
public void handleRequest(int leaveDays) {
if (leaveDays <= 1) {
System.out.println("主管批准请假 " + leaveDays + " 天");
} else if (next != null) {
next.handleRequest(leaveDays);
}
}
}
// 经理审批
public class ManagerHandler extends Handler {
@Override
public void handleRequest(int leaveDays) {
if (leaveDays <= 3) {
System.out.println("经理批准请假 " + leaveDays + " 天");
} else if (next != null) {
next.handleRequest(leaveDays);
}
}
}
// 总经理审批
public class BossHandler extends Handler {
@Override
public void handleRequest(int leaveDays) {
if (leaveDays <= 7) {
System.out.println("总经理批准请假 " + leaveDays + " 天");
} else {
System.out.println("请假天数太多,不批准!");
}
}
}
客户端构建责任链
public class Client {
public static void main(String[] args) {
Handler leader = new LeaderHandler();
Handler manager = new ManagerHandler();
Handler boss = new BossHandler();
// 组装责任链
leader.setNext(manager);
manager.setNext(boss);
// 发起请求
System.out.println("请假 1 天:");
leader.handleRequest(1);
System.out.println("\n请假 3 天:");
leader.handleRequest(3);
System.out.println("\n请假 5 天:");
leader.handleRequest(5);
System.out.println("\n请假 10 天:");
leader.handleRequest(10);
}
}
运行结果:
请假 1 天:
主管批准请假 1 天
请假 3 天:
经理批准请假 3 天
请假 5 天:
总经理批准请假 5 天
请假 10 天:
请假天数太多,不批准
5.4 总结
| 特点 | 说明 |
|---|---|
| 解耦 | 发起者不需要关心谁来处理请求 |
| 灵活扩展 | 想加新的审批人,直接在链中添加即可 |
| 顺序可配置 | 链的顺序可动态调整 |
| 缺点 | 调试较难,请求可能被“吞掉”不易追踪 |
该不该用责任链?
| 是否情况 | 说明 | 是否适合 |
|---|---|---|
| 逻辑步骤多、顺序强依赖 | 多个环节必须按顺序执行(如下单) | 适合 |
| 每个环节可独立复用 | 校验、规则、拦截器 | 适合 |
| 某些环节可能可选 | 动态启停、灰度控制 | 适合 |
| 所有逻辑都强耦合、共享状态多 | 各节点间互相依赖、结果传来传去 | 不适合 |
| 逻辑简单、固定流程 | 只有两三个 if 判断 | 不适合 |
5.5 实际应用场景
| 应用场景 | 举例 |
|---|---|
| Spring MVC | Filter 链 / Interceptor 链 |
| Netty | ChannelPipeline 责任链 |
| Spring AOP | 多个切面执行链 |
| 日志处理 | 多级日志处理器链 |
| 权限验证 | 多层权限过滤链 |
5.5.1 Spring MVC 中的 Filter 链
责任链位置:javax.servlet.FilterChain
工作流程:
当一个请求进入应用时:
request → Filter1 → Filter2 → Filter3 → Controller → Response
每个 Filter 都可以:
- 做一些前置处理(日志、鉴权、编码、跨域等)
- 调用
chain.doFilter(request, response)交给下一个 Filter
这是标准责任链模式:
- 每个 Filter 都是一个处理者(Handler)
- FilterChain 负责维护处理顺序和调用下一个
示例:
public class LogFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
System.out.println("进入日志过滤器");
chain.doFilter(req, res); // 放行
System.out.println("离开日志过滤器");
}
}
5.5.2 Spring MVC 中的 HandlerInterceptor 链
责任链位置:org.springframework.web.servlet.HandlerInterceptor
请求经过多个拦截器:
request → Interceptor1 → Interceptor2 → Controller
preHandle()可以中断请求postHandle()、afterCompletion()用于后置处理
完全是责任链思想:
- 每个拦截器只负责一件事(比如登录校验、日志、权限)
- 通过返回值控制链是否继续执行
示例:
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (request.getHeader("token") == null) {
response.setStatus(401);
return false; // 终止链
}
return true; // 放行
}
}
5.5.3 Spring AOP 切面调用链
Spring 在执行切面(Aspect)时,也会生成一个责任链调用结构。
比如你定义了 3 个切面:
@Transactional → @Around("log") → @Around("auth") → Controller
Spring 会把它们组合成一个 MethodInvocation 链:
每个切面都像责任链中的一个“节点”,执行完可以调用 proceed() 去触发下一个切面。
Object proceed() throws Throwable; // 类似 chain.doFilter()
这也是典型责任链实现。
5.5.4 Spring Security 的过滤器链
org.springframework.security.web.FilterChainProxy
Spring Security 是责任链模式的极致代表。
请求经过的典型流程:
SecurityContextPersistenceFilter
→ LogoutFilter
→ UsernamePasswordAuthenticationFilter
→ FilterSecurityInterceptor
→ ...
每个 Filter 都完成一部分安全逻辑:
- 从 Session 取用户信息
- 登录校验
- 权限判断
- 异常处理
Spring Security 自己维护一个「FilterChainProxy」,内部管理多条 Filter 链。
5.5.5 Spring Cloud Gateway 过滤器链
org.springframework.cloud.gateway.filter.GatewayFilterChain
网关转发请求时,也是一条责任链:
GlobalFilter1 → GlobalFilter2 → RouteFilter → ...
每个 Filter 执行逻辑,最后交给下一个。
5.6 扩展:使用职责链 + Lambda 改进版
如果你用的是 Java 8+,可以用函数式接口简化:
定义一个可拦截的链式接口
@FunctionalInterface
interface Handler {
boolean handle(int days); // 返回 true 表示已处理,false 交给下一个
}
动态组合责任链
public class LambdaChain {
public static void main(String[] args) {
Handler leader = (days) -> {
if (days <= 1) {
System.out.println("主管批准 " + days + " 天");
return true;
}
return false;
};
Handler manager = (days) -> {
if (days <= 3) {
System.out.println("经理批准 " + days + " 天");
return true;
}
return false;
};
Handler boss = (days) -> {
if (days <= 7) {
System.out.println("总经理批准 " + days + " 天");
return true;
}
System.out.println("请假太多,不批准");
return true;
};
// 组装链条(这里用函数式方式动态连接)
Handler chain = combine(leader, manager, boss);
// 执行
chain.handle(1);
chain.handle(4);
chain.handle(10);
}
static Handler combine(Handler... handlers) {
return (days) -> {
for (Handler handler : handlers) {
if (handler.handle(days)) return true; // 有人处理就中断
}
return false;
};
}
}
5.7 SpringBean注入搭配责任链
-
定义一个通用接口
public interface Handler<T> { boolean handle(T context); } -
定义一个责任链管理器
import org.springframework.stereotype.Component; import java.util.List; @Component public class HandlerChain<T> { private final List<Handler<T>> handlers; // Spring 会自动注入所有实现 Handler 的 Bean(按顺序) public HandlerChain(List<Handler<T>> handlers) { this.handlers = handlers; } public void execute(T context) { for (Handler<T> handler : handlers) { boolean continueNext = handler.handle(context); if (!continueNext) break; // 中断链 } } } -
定义多个 Handler 实现类
@Component public class ValidateHandler implements Handler<OrderContext> { @Override public boolean handle(OrderContext ctx) { if (ctx.getAmount() <= 0) { System.out.println("订单金额非法"); return false; } System.out.println("订单校验通过"); return true; } } @Component public class StockHandler implements Handler<OrderContext> { @Override public boolean handle(OrderContext ctx) { if (ctx.getStock() < ctx.getQuantity()) { System.out.println("库存不足"); return false; } System.out.println("库存充足"); return true; } } @Component public class PayHandler implements Handler<OrderContext> { @Override public boolean handle(OrderContext ctx) { System.out.println("执行支付逻辑"); return true; } } -
定义上下文对象
public class OrderContext { private int quantity; private int stock; private double amount; // 构造、get/set } -
使用时注入调用
@RestController public class OrderController { private final HandlerChain<OrderContext> handlerChain; public OrderController(HandlerChain<OrderContext> handlerChain) { this.handlerChain = handlerChain; } @PostMapping("/order") public String createOrder(@RequestBody OrderContext ctx) { handlerChain.execute(ctx); return "OK"; } }优点:
-
责任节点自动注册(Spring 管理)
-
可通过
@Order控制执行顺序 -
可随时新增、删除节点,无需改主流程
-
逻辑清晰:每个责任专注一件事(符合单一职责)
-
-
结合
@Order或Ordered控制执行顺序@Component @Order(1) public class ValidateHandler implements Handler<OrderContext> { ... } @Component @Order(2) public class StockHandler implements Handler<OrderContext> { ... } @Component @Order(3) public class PayHandler implements Handler<OrderContext> { ... }
六,命令模式
6.1 场景
假设你开了一家智能家居公司,第一代产品是遥控器,可以控制家里的灯。
第一代遥控器的代码:
// 灯的类
class Light {
public void on() {
System.out.println("灯打开了");
}
public void off() {
System.out.println("灯关闭了");
}
}
// 遥控器类
class SimpleRemoteControl {
Light light;
public void setLight(Light light) {
this.light = light;
}
public void onButtonPressed() {
light.on();
}
public void offButtonPressed() {
light.off();
}
}
问题来了:
- 如果想添加空调、电视、音响等设备,必须修改遥控器类
- 遥控器和具体设备(灯)紧密耦合
- 无法实现"撤销"功能
- 无法实现"宏命令"(一键同时开多个设备)
这就是命令模式要解决的问题!
6.2 命令模式的核心思想
命令模式 = 把 "请求" 封装成对象
把 "开灯" 这个操作,不直接调用 Light.on(),而是创建一个 "开灯命令对象",这个对象知道如何开灯。
四个核心角色:
- 命令接口 - 所有命令都实现这个接口
- 具体命令 - 执行具体的操作
- 接收者 - 真正干活的对象(灯、空调等)
- 调用者 - 发出命令的对象(遥控器)
6.3 具体实现
-
创建命令接口
// 所有命令都实现这个接口 public interface Command { void execute(); void undo(); } -
创建接收者(真正干活的设备)
// 灯 - 接收者 public class Light { private String location; public Light(String location) { this.location = location; } public void on() { System.out.println(location + " 灯打开了"); } public void off() { System.out.println(location + " 灯关闭了"); } } // 空调 - 接收者 public class AirConditioner { private int temperature = 26; public void on() { System.out.println("空调打开了,当前温度:" + temperature); } public void off() { System.out.println("空调关闭了"); } public void setTemperature(int temp) { this.temperature = temp; System.out.println("空调温度设置为:" + temperature); } } -
创建具体命令
// 开灯命令 public class LightOnCommand implements Command { private Light light; public LightOnCommand(Light light) { this.light = light; } @Override public void execute() { light.on(); } @Override public void undo() { light.off(); } } // 关灯命令 public class LightOffCommand implements Command { private Light light; public LightOffCommand(Light light) { this.light = light; } @Override public void execute() { light.off(); } @Override public void undo() { light.on(); } } // 开空调命令 public class AirConditionerOnCommand implements Command { private AirConditioner ac; public AirConditionerOnCommand(AirConditioner ac) { this.ac = ac; } @Override public void execute() { ac.on(); } @Override public void undo() { ac.off(); } } // 空调温度设置命令 public class AirConditionerSetTempCommand implements Command { private AirConditioner ac; private int temperature; private int previousTemperature; // 记住之前温度,用于撤销 public AirConditionerSetTempCommand(AirConditioner ac, int temperature) { this.ac = ac; this.temperature = temperature; } @Override public void execute() { previousTemperature = 26; // 假设默认温度是26 ac.setTemperature(temperature); } @Override public void undo() { ac.setTemperature(previousTemperature); } } -
创建调用者(遥控器)
// 智能遥控器 - 调用者 public class SmartRemoteControl { Command[] onCommands; Command[] offCommands; Command undoCommand; // 记录上一个执行的命令 public SmartRemoteControl() { // 假设有7个插槽 onCommands = new Command[7]; offCommands = new Command[7]; // 初始化所有插槽为空命令(避免空指针) Command noCommand = new NoCommand(); for (int i = 0; i < 7; i++) { onCommands[i] = noCommand; offCommands[i] = noCommand; } undoCommand = noCommand; } // 设置命令到指定插槽 public void setCommand(int slot, Command onCommand, Command offCommand) { onCommands[slot] = onCommand; offCommands[slot] = offCommand; } // 按下开按钮 public void onButtonPressed(int slot) { onCommands[slot].execute(); undoCommand = onCommands[slot]; } // 按下关按钮 public void offButtonPressed(int slot) { offCommands[slot].execute(); undoCommand = offCommands[slot]; } // 撤销按钮 public void undoButtonPressed() { undoCommand.undo(); } } // 空命令:什么都不做,用于初始化 public class NoCommand implements Command { @Override public void execute() {} @Override public void undo() {} } -
宏命令(一次执行多个命令)
// 宏命令:包含一组命令 public class MacroCommand implements Command { Command[] commands; public MacroCommand(Command[] commands) { this.commands = commands; } @Override public void execute() { for (Command command : commands) { command.execute(); } } @Override public void undo() { for (Command command : commands) { command.undo(); } } }
测试
public class RemoteControlTest {
public static void main(String[] args) {
// 1. 创建接收者(设备)
Light livingRoomLight = new Light("客厅");
Light bedroomLight = new Light("卧室");
AirConditioner ac = new AirConditioner();
// 2. 创建命令
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
LightOnCommand bedroomLightOn = new LightOnCommand(bedroomLight);
LightOffCommand bedroomLightOff = new LightOffCommand(bedroomLight);
AirConditionerOnCommand acOn = new AirConditionerOnCommand(ac);
AirConditionerSetTempCommand acSetTemp = new AirConditionerSetTempCommand(ac, 24);
// 3. 创建调用者(遥控器)
SmartRemoteControl remote = new SmartRemoteControl();
// 4. 设置命令到遥控器插槽
remote.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remote.setCommand(1, bedroomLightOn, bedroomLightOff);
remote.setCommand(2, acOn, null); // 空调只有开,没有专门关的命令
// 5. 测试
System.out.println("=== 测试遥控器 ===");
remote.onButtonPressed(0); // 开客厅灯
remote.offButtonPressed(0); // 关客厅灯
remote.undoButtonPressed(); // 撤销:开客厅灯
remote.onButtonPressed(1); // 开卧室灯
remote.onButtonPressed(2); // 开空调
// 6. 测试宏命令
System.out.println("\n=== 测试宏命令(一键离家模式)===");
Command[] leaveHomeCommands = {
new LightOffCommand(livingRoomLight),
new LightOffCommand(bedroomLight),
new AirConditionerOnCommand(ac) // 为了简化,这里用关空调的命令更好
};
MacroCommand leaveHomeMacro = new MacroCommand(leaveHomeCommands);
remote.setCommand(5, leaveHomeMacro, null);
remote.onButtonPressed(5); // 一键执行离家模式
remote.undoButtonPressed(); // 撤销离家模式
}
}
6.4 命令模式的优缺点
优点
- 解耦:调用者和接收者完全解耦
- 扩展性:增加新命令不需要修改现有代码
- 可组合:可以将多个命令组合成宏命令
- 可撤销:轻松实现撤销/重做功能
- 队列:可以将命令排队,实现延迟执行
缺点
- 类爆炸:每个操作都要创建一个命令类
- 学习曲线:比直接调用更复杂
6.5 实际应用场景
- GUI按钮操作:每个按钮对应一个命令
- 数据库事务:每个操作可提交、回滚
- 任务调度:将命令放入队列,按顺序执行
- 日志系统:记录命令以便恢复系统状态
- 网络请求:将请求封装成命令发送
总结
命令模式的核心本质:将请求封装成对象
- 命令是对象,不是方法
- 调用者不知道谁在执行
- 执行者不知道谁在调用
- 方便撤销和排队
命令模式 = 调用者 → 【中间商(命令对象)】 → 执行者
↑_________________|
中间商赚的差价:
1. 存储历史记录
2. 支持回退
3. 延迟执行
4. 排队执行
6.6 实际业务场景举例
业务需求
- 用户下单后,如果30分钟内未支付,系统自动取消订单
- 取消时要:
- 恢复商品库存
- 返还优惠券
- 发送取消通知给用户
- 支持手动取消(用户主动取消)
- 支持批量取消(管理员清退异常订单)
6.6.1 第一版:不用模式的代码(痛点展示)
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepo;
@Autowired
private InventoryService inventoryService;
@Autowired
private CouponService couponService;
@Autowired
private NotificationService notificationService;
// 定时任务:每分钟执行一次,查找超时未支付订单
@Scheduled(cron = "0 */1 * * * ?")
public void autoCancelTimeoutOrders() {
List<Order> timeoutOrders = orderRepo.findTimeoutUnpaidOrders(30);
for (Order order : timeoutOrders) {
// 1. 修改订单状态
order.setStatus(OrderStatus.CANCELLED);
order.setCancelReason("超时未支付");
orderRepo.save(order);
// 2. 恢复库存
for (OrderItem item : order.getItems()) {
inventoryService.increaseStock(item.getSkuId(), item.getQuantity());
}
// 3. 返还优惠券
if (order.getCouponId() != null) {
couponService.restoreCoupon(order.getCouponId());
}
// 4. 发送通知
notificationService.send(order.getUserId(),
"您的订单" + order.getOrderNo() + "已超时取消");
}
}
// 用户手动取消
public void manualCancel(Long orderId, Long userId) {
Order order = orderRepo.findById(orderId);
// 权限校验...
// 复制粘贴了和上面几乎一样的代码!!!
order.setStatus(OrderStatus.CANCELLED);
order.setCancelReason("用户手动取消");
orderRepo.save(order);
for (OrderItem item : order.getItems()) {
inventoryService.increaseStock(item.getSkuId(), item.getQuantity());
}
if (order.getCouponId() != null) {
couponService.restoreCoupon(order.getCouponId());
}
notificationService.send(order.getUserId(),
"您已成功取消订单" + order.getOrderNo());
}
// 管理员批量取消
public void batchCancel(List<Long> orderIds) {
// 又复制一遍...代码开始失控
}
}
痛点:
- 代码重复:三个取消方法逻辑基本一致
- 违背开闭原则:想增加"取消时返还积分"要改3个地方
- 难以测试:取消逻辑和定时任务耦合
- 无法复用:取消逻辑无法单独复用
- 无法追溯:不知道谁取消了订单,为什么取消
6.6.2 用命令模式重构
-
创建命令接口
// 所有取消命令都实现这个接口 public interface OrderCancelCommand { void execute(); void undo(); // 万一取消错了?支持恢复订单 String getOrderNo(); String getCancelSource(); // 取消来源:超时/用户/管理员 } -
创建接收者(各个业务服务)
// 这些@Service都已经存在,不用动 @Service public class OrderStatusService { public void cancel(String orderNo, String reason) { // 修改订单状态为已取消 System.out.println("订单" + orderNo + "已取消,原因:" + reason); } public void restore(String orderNo) { // 恢复订单为待支付 System.out.println("订单" + orderNo + "已恢复为待支付"); } } @Service public class InventoryService { public void restoreStock(String skuId, Integer quantity) { System.out.println("库存恢复:" + skuId + " +" + quantity); } } @Service public class CouponService { public void restoreCoupon(String couponId) { System.out.println("优惠券返还:" + couponId); } } @Service public class NotificationService { public void sendCancelMessage(String userId, String orderNo, String reason) { System.out.println("发送取消通知给用户" + userId + ",订单" + orderNo + ",原因:" + reason); } } @Service public class PointsService { public void restorePoints(String userId, Integer points) { System.out.println("积分返还:" + userId + " +" + points); } } -
创建具体的取消命令
@Component @Scope("prototype") // 每个订单创建新实例 public class OrderCancelCommandImpl implements OrderCancelCommand { // 命令需要的参数 private String orderNo; private String userId; private String couponId; private List<OrderItem> items; private Integer points; private String cancelSource; private String cancelReason; // 注入所有接收者(业务服务) @Autowired private OrderStatusService orderStatusService; @Autowired private InventoryService inventoryService; @Autowired private CouponService couponService; @Autowired private NotificationService notificationService; @Autowired private PointsService pointsService; // 初始化命令数据 public void init(Order order, String cancelSource, String cancelReason) { this.orderNo = order.getOrderNo(); this.userId = order.getUserId(); this.couponId = order.getCouponId(); this.items = order.getItems(); this.points = order.getUsedPoints(); this.cancelSource = cancelSource; this.cancelReason = cancelReason; } @Override public void execute() { // 1. 改订单状态 orderStatusService.cancel(orderNo, cancelReason); // 2. 恢复库存 for (OrderItem item : items) { inventoryService.restoreStock(item.getSkuId(), item.getQuantity()); } // 3. 返还优惠券 if (couponId != null) { couponService.restoreCoupon(couponId); } // 4. 返还积分 if (points > 0) { pointsService.restorePoints(userId, points); } // 5. 发通知 notificationService.sendCancelMessage(userId, orderNo, cancelReason); // 6. 记录取消日志 System.out.println("【操作日志】订单" + orderNo + "被取消,来源:" + cancelSource + ",原因:" + cancelReason); } @Override public void undo() { // 恢复订单为待支付状态 orderStatusService.restore(orderNo); System.out.println("已撤销取消操作,订单" + orderNo + "恢复"); } @Override public String getOrderNo() { return orderNo; } @Override public String getCancelSource() { return cancelSource; } } -
创建调用者(命令执行器)
@Component public class OrderCancelInvoker { // 存储最近执行的命令,用于撤销 private Stack<OrderCancelCommand> commandHistory = new Stack<>(); // 存储每个订单当前的取消状态 private Map<String, OrderCancelCommand> orderCancelMap = new ConcurrentHashMap<>(); // 执行取消命令 public void executeCommand(OrderCancelCommand command) { command.execute(); commandHistory.push(command); orderCancelMap.put(command.getOrderNo(), command); } // 撤销最近一次取消 public void undo() { if (!commandHistory.isEmpty()) { OrderCancelCommand command = commandHistory.pop(); command.undo(); orderCancelMap.remove(command.getOrderNo()); } } // 撤销指定订单的取消 public void undoByOrderNo(String orderNo) { OrderCancelCommand command = orderCancelMap.get(orderNo); if (command != null) { command.undo(); orderCancelMap.remove(orderNo); // 从历史栈中移除 commandHistory.remove(command); } } // 获取取消统计 public Map<String, Long> getCancelStatistics() { return commandHistory.stream() .collect(Collectors.groupingBy( OrderCancelCommand::getCancelSource, Collectors.counting() )); } } -
在业务层使用
@Service public class OrderService { @Autowired private OrderCancelInvoker cancelInvoker; @Autowired private OrderRepository orderRepo; @Autowired private ApplicationContext applicationContext; // 用于创建prototype bean // 1. 自动取消超时订单 @Scheduled(cron = "0 */1 * * * ?") public void autoCancelTimeoutOrders() { List<Order> timeoutOrders = orderRepo.findTimeoutUnpaidOrders(30); for (Order order : timeoutOrders) { // 从Spring容器获取新的命令实例 OrderCancelCommandImpl command = applicationContext.getBean(OrderCancelCommandImpl.class); command.init(order, "SYSTEM", "超时未支付"); // 执行取消 cancelInvoker.executeCommand(command); } } // 2. 用户手动取消 public void manualCancel(String orderNo, Long userId) { Order order = orderRepo.findByOrderNo(orderNo); // 权限校验... OrderCancelCommandImpl command = applicationContext.getBean(OrderCancelCommandImpl.class); command.init(order, "USER", "用户主动取消"); cancelInvoker.executeCommand(command); } // 3. 管理员批量取消 public void batchCancel(List<String> orderNos, String adminId) { List<Order> orders = orderRepo.findByOrderNos(orderNos); for (Order order : orders) { OrderCancelCommandImpl command = applicationContext.getBean(OrderCancelCommandImpl.class); command.init(order, "ADMIN", "管理员批量清理"); cancelInvoker.executeCommand(command); } } // 4. 新需求:支付超时提醒(1分钟前) @Scheduled(cron = "0 */1 * * * ?") public void remindTimeoutOrders() { List<Order> remindOrders = orderRepo.findWillTimeoutOrders(29); // 完全独立于取消逻辑,不需要修改取消命令 for (Order order : remindOrders) { notificationService.sendTimeoutRemind(order.getUserId(), order.getOrderNo()); } } } -
控制层提供撤销功能
@RestController @RequestMapping("/api/orders") public class OrderController { @Autowired private OrderService orderService; @Autowired private OrderCancelInvoker cancelInvoker; // 取消订单 @PostMapping("/{orderNo}/cancel") public Result cancelOrder(@PathVariable String orderNo) { orderService.manualCancel(orderNo, getCurrentUserId()); return Result.success("订单取消成功"); } // 撤销取消(恢复订单) @PostMapping("/{orderNo}/undo-cancel") public Result undoCancel(@PathVariable String orderNo) { cancelInvoker.undoByOrderNo(orderNo); return Result.success("已恢复订单"); } // 获取取消统计 @GetMapping("/cancel-statistics") public Result getStatistics() { return Result.success(cancelInvoker.getCancelStatistics()); } }
注意:这里的存储最近执行的命令,我用的是本地 jvm 的变量存储,但在实际业务场景一般用数据库 /Redis/MQ 来进行存储!
七,状态模式
7.1 状态模式
想象一下你家里的电风扇:
- 当你按下"开关"按钮时,风扇会在不同状态间切换
- 停止状态 → 低速状态 → 中速状态 → 高速状态 → 停止状态
- 在不同状态下,按同一个按钮的行为是不同的
这就是状态模式的核心思想:允许对象在内部状态改变时改变它的行为,看起来就像对象改变了它的类
7.2 为什么需要状态模式?
假设不用状态模式,我们可能会这样写代码:
public class Fan {
public static final int OFF = 0;
public static final int LOW = 1;
public static final int MEDIUM = 2;
public static final int HIGH = 3;
private int state = OFF;
public void pull() {
if (state == OFF) {
state = LOW;
System.out.println("低速档");
} else if (state == LOW) {
state = MEDIUM;
System.out.println("中速档");
} else if (state == MEDIUM) {
state = HIGH;
System.out.println("高速档");
} else if (state == HIGH) {
state = OFF;
System.out.println("关闭");
}
}
}
问题:
- 当要添加新状态(比如"睡眠风")时,必须修改pull()方法
- 状态转换逻辑分散在大量if-else中
- 违反"开闭原则"
7.3 状态模式的基本结构
状态模式把状态和行为封装成独立的类
┌─────────────────┐ ┌─────────────────┐
│ Context │ │ State │
│ (上下文/环境) │──────│ (抽象状态) │
├─────────────────┤ ├─────────────────┤
│ -state: State │ │ +handle() │
│ +request() │ └─────────────────┘
└─────────────────┘ ▲
│
┌────────────────┼────────────────┐
│ │ │
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ ConcreteStateA│ │ ConcreteStateB│ │ ConcreteStateC│
│ (具体状态A) │ │ (具体状态B) │ │ (具体状态C) │
├──────────────┤ ├──────────────┤ ├──────────────┤
│ +handle() │ │ +handle() │ │ +handle() │
└──────────────┘ └──────────────┘ └──────────────┘
7.4 用状态模式重写风扇例子
第一步:定义状态接口
// 状态接口
public interface FanState {
void pull(FanContext context); // 拉一下开关
}
第二步:实现具体状态类
// 关闭状态
public class OffState implements FanState {
@Override
public void pull(FanContext fan) {
System.out.println("从关闭状态切换到低速档");
fan.setState(new LowState()); // 切换到低速状态
}
}
// 低速状态
public class LowState implements FanState {
@Override
public void pull(FanContext fan) {
System.out.println("从低速档切换到中速档");
fan.setState(new MediumState());
}
}
// 中速状态
public class MediumState implements FanState {
@Override
public void pull(FanContext fan) {
System.out.println("从中速档切换到高速档");
fan.setState(new HighState());
}
}
// 高速状态
public class HighState implements FanState {
@Override
public void pull(FanContext fan) {
System.out.println("从高速档切换到关闭状态");
fan.setState(new OffState());
}
}
第三步:创建上下文类(风扇)
public class FanContext {
private FanState currentState;
public FanContext() {
// 初始状态:关闭
currentState = new OffState();
}
public void setState(FanState state) {
this.currentState = state;
}
public void pull() {
// 委托给当前状态对象处理
currentState.pull(this);
}
}
第四步:测试代码
public class Test {
public static void main(String[] args) {
FanContext fan = new FanContext();
fan.pull(); // 关闭 → 低速
fan.pull(); // 低速 → 中速
fan.pull(); // 中速 → 高速
fan.pull(); // 高速 → 关闭
fan.pull(); // 关闭 → 低速
}
}
7.5 具体业务场景 - 订单状态
// 订单状态接口
public interface OrderState {
void pay(Order order); // 支付
void ship(Order order); // 发货
void confirm(Order order); // 确认收货
void cancel(Order order); // 取消
}
// 待支付状态
public class PendingState implements OrderState {
@Override
public void pay(Order order) {
System.out.println("支付成功,订单状态变为【已支付】");
order.setState(new PaidState());
}
@Override
public void ship(Order order) {
System.out.println("请先完成支付才能发货");
}
@Override
public void confirm(Order order) {
System.out.println("请先支付再确认收货");
}
@Override
public void cancel(Order order) {
System.out.println("订单已取消");
order.setState(new CancelledState());
}
}
// 已支付状态
public class PaidState implements OrderState {
@Override
public void pay(Order order) {
System.out.println("订单已支付,请勿重复支付");
}
@Override
public void ship(Order order) {
System.out.println("发货成功,订单状态变为【已发货】");
order.setState(new ShippedState());
}
@Override
public void confirm(Order order) {
System.out.println("请等待发货后再确认收货");
}
@Override
public void cancel(Order order) {
System.out.println("退款申请中,订单已取消");
order.setState(new CancelledState());
}
}
// 订单类
public class Order {
private OrderState state;
public Order() {
this.state = new PendingState(); // 初始状态:待支付
}
public void setState(OrderState state) {
this.state = state;
}
public void pay() {
state.pay(this);
}
public void ship() {
state.ship(this);
}
public void confirm() {
state.confirm(this);
}
public void cancel() {
state.cancel(this);
}
}
八,迭代器模式
8.1 什么是迭代器模式?
想象你有一个书架,上面摆满了书。你想一本一本的看完这些书,但你不关心书架的内部结构(书是怎么排列的、有没有编号),你只希望有一个简单的方法能“拿到下一本书”。
迭代器模式就是为解决这个问题而生的:它提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
核心思想:将遍历的责任从集合对象中分离出来,交给一个专门的迭代器对象。
8.2 为什么需要迭代器模式?
看一个反例:如果没有迭代器模式,你可能这样遍历:
// 不封装的做法
public class BookShelf {
private Book[] books;
private int last = 0;
public Book getBookAt(int index) {
return books[index];
}
public int getLength() {
return last;
}
}
// 客户端遍历
BookShelf bookShelf = new BookShelf();
for (int i = 0; i < bookShelf.getLength(); i++) {
Book book = bookShelf.getBookAt(i);
System.out.println(book.getName());
}
问题:
- 客户端需要知道集合的内部结构(数组、索引)
- 如果将来书架改成用
List存储,客户端代码也要改 - 难以支持多种遍历方式(如反向遍历、条件遍历)
迭代器模式解决这些问题:客户端只和迭代器打交道,不需要知道集合的具体实现。
8.3 迭代器模式的结构
┌─────────────────┐ ┌─────────────────┐
│ <<interface>> │ │ <<interface>> │
│ Aggregate │ │ Iterator │
├─────────────────┤ ├─────────────────┤
│ +iterator() │────────▶│ +hasNext():bool │
└─────────────────┘ │ +next():Object │
▲ └─────────────────┘
│ ▲
│ │
┌─────────────────┐ ┌─────────────────┐
│ ConcreteAggregate│ │ ConcreteIterator│
├─────────────────┤ ├─────────────────┤
│ +iterator() │────────▶│ -aggregate │
└─────────────────┘ │ -index │
│ +hasNext() │
│ +next() │
└─────────────────┘
- Iterator(迭代器接口):定义访问和遍历元素的接口,通常有
hasNext(),next()等方法。 - ConcreteIterator(具体迭代器):实现迭代器接口,记录遍历的当前位置。
- Aggregate(聚合接口):定义创建迭代器对象的接口,通常有一个
iterator()方法。 - ConcreteAggregate(具体聚合):实现聚合接口,返回一个合适的
ConcreteIterator实例。
8.4 代码示例——书架和书的迭代器
定义 Book 类
public class Book {
private String name;
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
定义迭代器接口
public interface Iterator {
boolean hasNext();
Object next();
}
定义聚合接口
public interface Aggregate {
Iterator iterator();
}
实现具体聚合(书架)
public class BookShelf implements Aggregate {
private Book[] books;
private int last = 0;
public BookShelf(int maxSize) {
this.books = new Book[maxSize];
}
public Book getBookAt(int index) {
return books[index];
}
public void appendBook(Book book) {
this.books[last] = book;
last++;
}
public int getLength() {
return last;
}
@Override
public Iterator iterator() {
return new BookShelfIterator(this);
}
}
实现具体迭代器
public class BookShelfIterator implements Iterator {
private BookShelf bookShelf;
private int index;
public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
this.index = 0;
}
@Override
public boolean hasNext() {
return index < bookShelf.getLength();
}
@Override
public Object next() {
Book book = bookShelf.getBookAt(index);
index++;
return book;
}
}
客户端使用
public class Main {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf(4);
bookShelf.appendBook(new Book("设计模式"));
bookShelf.appendBook(new Book("Java编程思想"));
bookShelf.appendBook(new Book("Effective Java"));
bookShelf.appendBook(new Book("重构"));
Iterator it = bookShelf.iterator();
while (it.hasNext()) {
Book book = (Book) it.next();
System.out.println(book.getName());
}
}
}
输出:
设计模式
Java编程思想
Effective Java
重构
8.5 迭代器模式在Java中的应用
Java 集合框架(Collection Framework)大量使用了迭代器模式。
java.util.Iterator接口:hasNext(),next(),remove()java.util.ListIterator扩展了Iterator,增加了反向遍历等功能- 所有
Collection子类都实现了Iterable接口,提供iterator()方法,因此可以使用增强 for 循环(for-each)
List<String> list = Arrays.asList("A", "B", "C");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
// 或者使用 for-each(语法糖,底层也是迭代器)
for (String s : list) {
System.out.println(s);
}
8.6 迭代器的种类
- 外部迭代器:由客户端控制遍历过程,如
while(it.hasNext()) { it.next(); }。上面例子就是外部迭代器。 - 内部迭代器:由迭代器内部完成遍历,客户端只需传入要执行的操作(如回调函数)。例如 JavaScript 的
forEach,Java 8 的Iterable.forEach()。
// Java 8 内部迭代器
list.forEach(System.out::println);
8.7 优缺点
优点:
- 简化遍历:客户端无需了解集合内部结构,只需使用统一方法。
- 支持多种遍历:可以为同一个集合提供不同迭代器(如正向、反向、过滤迭代器)。
- 封装性好:集合内部数据可以安全地被隐藏,迭代器负责遍历逻辑。
- 符合开闭原则:增加新的迭代器无需修改集合类。
缺点:
- 增加类数目:每个集合都需要对应的迭代器类,可能增加系统复杂度。
- 对于简单集合可能过度设计:如只需遍历数组,直接 for 循环更简单。
九,中介者模式
9.1 什么是中介者模式?
中介者模式(Mediator Pattern)是一种行为设计模式,它通过引入一个中介者对象来封装一组对象之间的交互,使得这些对象不需要显式地相互引用,从而降低耦合,并可以独立地改变它们之间的交互。
核心思想:将多对多的通信转化为一对多的通信,让中介者对象协调各个对象(称为同事类)之间的协作。
9.2 为什么需要中介者模式?
问题场景:想象一个复杂的系统,比如机场塔台调度系统。飞机(同事类)之间需要知道彼此的飞行状态来避免碰撞,但如果每架飞机都与其他所有飞机通信,会形成复杂的网状结构,耦合度高,难以维护和扩展。
// 没有中介者的糟糕设计
public class Aircraft {
private List<Aircraft> otherAircrafts;
public void changeAltitude(int newAltitude) {
this.altitude = newAltitude;
for (Aircraft other : otherAircrafts) {
other.receiveAlert(this, newAltitude);
}
}
}
问题:
- 每个对象都需要知道其他所有对象,耦合度过高。
- 对象越多,通信越复杂,难以维护。
- 增加或删除一个对象需要修改所有相关对象的代码。
中介者模式通过引入一个中介者(如塔台)来解决这些问题:所有飞机只与塔台通信,塔台负责协调它们之间的交互。这样,飞机之间不再直接耦合,系统更容易理解和扩展
9.3 中介者模式的结构
┌─────────────────────────────────────────────────────────────┐
│ Mediator │
│ (中介者接口) │
├─────────────────────────────────────────────────────────────┤
│ +notify(sender: Colleague, event: String) │
└─────────────────────────────────────────────────────────────┘
▲
│
┌─────────────┼─────────────┐
│ │ │
┌───────────────────┐ ┌───────────────────┐
│ ConcreteMediator │ │ Colleague │
│ (具体中介者) │ │ (同事类接口) │
├───────────────────┤ ├───────────────────┤
│ -colleagues │ │ +setMediator() │
│ +notify() │ │ +action() │
└───────────────────┘ └───────────────────┘
▲
│
┌───────────────┼───────────────┐
│ │ │
┌───────────────────┐ ┌───────────────────┐
│ ConcreteColleague1│ │ ConcreteColleague2│
│ (具体同事类1) │ │ (具体同事类2) │
├───────────────────┤ ├───────────────────┤
│ +action1() │ │ +action2() │
│ +send() │ │ +send() │
└───────────────────┘ └───────────────────┘
- Mediator(中介者接口):声明一个用于与各同事对象通信的方法,通常是一个
notify或send方法。 - ConcreteMediator(具体中介者):实现中介者接口,维护对各个同事对象的引用,协调它们的交互。
- Colleague(同事类接口):定义同事类的公共接口,通常持有一个对中介者的引用(但不持有其他同事的引用)。
- ConcreteColleague(具体同事类):实现自己的业务逻辑,当需要与其他同事通信时,通过中介者进行,而不是直接调用。
9.4 代码实现
假设我们有一个智能家居系统,包含灯光、空调、窗帘等设备。它们之间需要协作:例如,当灯光打开时,如果光线充足,自动关闭窗帘;当空调打开时,自动关闭窗户。用中介者模式实现。
定义中介者接口
// 中介者接口
public interface SmartHomeMediator {
void notify(String event, Device sender);
}
定义同事类(设备)基类
// 抽象同事类
public abstract class Device {
protected SmartHomeMediator mediator;
protected String name;
public Device(String name, SmartHomeMediator mediator) {
this.name = name;
this.mediator = mediator;
}
// 发送事件到中介者
public void sendEvent(String event) {
mediator.notify(event, this);
}
// 接收来自中介者的指令
public abstract void receive(String instruction);
public String getName() {
return name;
}
}
实现具体设备
// 灯光设备
public class Light extends Device {
private boolean isOn = false;
public Light(String name, SmartHomeMediator mediator) {
super(name, mediator);
}
public void turnOn() {
if (isOn) {
System.out.println(name + " 已经是打开状态,无需重复操作");
return;
}
isOn = true;
System.out.println(name + " 已打开");
sendEvent("light_on");
}
public void turnOff() {
if (!isOn) {
System.out.println(name + " 已经是关闭状态");
return;
}
isOn = false;
System.out.println(name + " 已关闭");
sendEvent("light_off");
}
public boolean isOn() {
return isOn;
}
@Override
public void receive(String instruction) {
if ("auto_adjust".equals(instruction)) {
System.out.println(name + " 接收到自动调节指令");
}
}
}
// 窗帘设备
public class Curtain extends Device {
private boolean isOpen = true;
public Curtain(String name, SmartHomeMediator mediator) {
super(name, mediator);
}
public void open() {
if (isOpen) {
System.out.println(name + " 已经是打开状态");
return;
}
isOpen = true;
System.out.println(name + " 已打开");
sendEvent("curtain_opened");
}
public void close() {
if (!isOpen) {
System.out.println(name + " 已经是关闭状态");
return;
}
isOpen = false;
System.out.println(name + " 已关闭");
sendEvent("curtain_closed");
}
public boolean isOpen() {
return isOpen;
}
@Override
public void receive(String instruction) {
if ("close".equals(instruction)) {
close();
} else if ("open".equals(instruction)) {
open();
}
}
}
// 空调设备
public class AirConditioner extends Device {
private int temperature = 25;
public AirConditioner(String name, SmartHomeMediator mediator) {
super(name, mediator);
}
public void setTemperature(int temp) {
this.temperature = temp;
System.out.println(name + " 温度设为 " + temp + "度");
sendEvent("ac_temperature_changed");
}
@Override
public void receive(String instruction) {
if ("close_window".equals(instruction)) {
System.out.println(name + " 接收指令:请关闭窗户以保持温度");
}
}
}
实现具体中介者
// 具体中介者:协调各个设备
public class SmartHomeMediatorImpl implements SmartHomeMediator {
private Light light;
private AirConditioner ac;
private Curtain curtain;
// 还可以有其他设备...
public void setLight(Light light) { this.light = light; }
public void setAirConditioner(AirConditioner ac) { this.ac = ac; }
public void setCurtain(Curtain curtain) { this.curtain = curtain; }
@Override
public void notify(String event, Device sender) {
System.out.println("中介者收到事件 [" + event + "] 来自 " + sender.getName());
// 根据事件协调其他设备
if ("light_on".equals(event)) {
// 如果灯开了,且是白天,则关闭窗帘
if (isDayTime()) {
curtain.receive("close");
}
} else if ("light_off".equals(event)) {
// 如果灯关了,可以考虑打开窗帘
curtain.receive("open");
} else if ("ac_temperature_changed".equals(event)) {
// 空调温度改变,提醒关闭窗户
// 假设有窗户设备...
System.out.println("中介者提醒:请关闭窗户以节能");
} else if ("curtain_closed".equals(event)) {
// 窗帘关闭后,如果灯没开,可以考虑自动开灯
if (!lightIsOn()) {
light.turnOn();
}
}
}
private boolean isDayTime() {
// 模拟判断白天黑夜
return true;
}
private boolean lightIsOn() {
// 这里可以维护状态,或者直接询问 light
return false; // 简化
}
}
客户端使用
public class HomeAutomationApp {
public static void main(String[] args) {
// 创建中介者
SmartHomeMediatorImpl mediator = new SmartHomeMediatorImpl();
// 创建设备,并注入中介者
Light light = new Light("客厅灯", mediator);
AirConditioner ac = new AirConditioner("客厅空调", mediator);
Curtain curtain = new Curtain("客厅窗帘", mediator);
// 设置中介者中的设备引用(中介者需要知道所有设备)
mediator.setLight(light);
mediator.setAirConditioner(ac);
mediator.setCurtain(curtain);
// 用户操作:打开灯
light.turnOn();
// 输出:
// 客厅灯 已打开
// 中介者收到事件 [light_on] 来自 客厅灯
// 客厅窗帘 已关闭 (因为白天,中介者自动关了窗帘)
// 用户操作:调节空调温度
ac.setTemperature(22);
// 输出:
// 客厅空调 温度设为 22度
// 中介者收到事件 [ac_temperature_changed] 来自 客厅空调
// 中介者提醒:请关闭窗户以节能
}
}
9.5 中介者模式的优缺点
优点:
- 降低耦合:将对象之间的多对多关系转变为一对多关系,每个对象只与中介者交互,易于理解和管理。
- 集中控制交互:交互逻辑集中在中介者中,便于修改和维护,避免将逻辑分散在各个对象中。
- 简化对象协议:用一对多的交互替代多对多的网状结构,使对象更简洁。
- 提高可复用性:同事类可以独立变化和复用,无需关心其他同事。
缺点:
- 中介者可能变得庞大复杂:如果系统中同事类很多,交互逻辑复杂,中介者可能变成“上帝对象”,难以维护。
- 中介者替换困难:中介者封装了所有交互逻辑,如果需要改变交互方式,可能需要修改中介者类,影响整个系统。
9.6 适用场景
- 系统中存在复杂的网状对象关系,导致结构混乱且难以复用。
- 想通过一个中间类来封装多个类中的行为,但又不想生成太多子类。
- 系统各个对象之间的交互方式经常变化,但又不想修改各个对象本身。
实际业务场景举例:
- 聊天室:用户通过聊天室(中介者)发送消息,而不是用户之间直接发送。
- 机场塔台调度:飞机通过塔台协调起飞降落,不直接通信。
- MVC架构中的Controller:Controller 作为中介者,协调 View 和 Model 的交互。
- GUI 组件交互:如对话框中的按钮、文本框、列表框等组件,通过一个中介者对象协调它们的行为,避免组件之间直接耦合。
9.7 扩展
中介者模式在 Java 中的应用:
java.util.Timer类的schedule()方法可以看作中介者,协调定时任务和执行线程。java.util.concurrent.ExecutorService也类似,提交的任务由线程池协调执行。
什么时候避免使用:
- 如果对象之间的交互本来就很直接简单,无需引入中介者增加复杂度。
- 如果中介者变得过于庞大,可以考虑拆分成多个中介者,或者使用事件总线(Event Bus)等替代方案。
十,备忘录模式
10.1 什么是备忘录模式?
备忘录模式(Memento Pattern)是一种行为设计模式,它允许在不破坏封装性的前提下,捕获并外部化一个对象的内部状态,以便在将来某个时刻可以将该对象恢复到原先保存的状态。
简单来说,它提供了 **“后悔药”** 的功能——当你对某个对象做了一系列操作后,可以随时回退到之前的某个状态。
10.2 为什么需要备忘录模式?
考虑一个文本编辑器,支持撤销操作。每次用户输入文字后,编辑器都需要保存当前状态,以便用户按 Ctrl+Z 时可以恢复到之前的状态。如果直接把状态暴露给外部(比如用 public 字段),虽然可以保存,但会破坏封装,导致对象内部细节对外公开,难以维护和变更。如果由编辑器自己管理状态历史,又会增加复杂度,违反单一职责原则。
备忘录模式很好地解决了这个问题:它让原发器自己负责状态的保存和恢复,而由负责人来管理历史记录,双方通过备忘录对象进行交互,备忘录封装了原发器的内部状态,对外部不可见。
10.3 备忘录模式的结构
备忘录模式包含三个核心角色:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Originator │ │ Memento │ │ Caretaker │
│ (原发器) │──────│ (备忘录) │──────│ (负责人) │
├─────────────┤ ├─────────────┤ ├─────────────┤
│ -state │ │ -state │ │ -memento │
│ +saveToMemento()│ │ +getState()│ │ +addMemento()│
│ +restoreFromMemento()│ └─────────────┘ │ +getMemento()│
└─────────────┘ └─────────────┘
- Originator(原发器):需要保存和恢复状态的对象。它负责创建一个备忘录(记录当前状态),以及使用备忘录恢复状态。
- Memento(备忘录):用于存储原发器内部状态的对象。它通常只允许原发器访问其内部数据,对外部提供有限的接口(比如只读)。
- Caretaker(负责人):负责保存备忘录,但不能对备忘录的内容进行操作或检查。它可以存储多个备忘录,以实现多次撤销。
10.4 代码示例——游戏角色状态存档
假设我们有一个游戏角色,它有生命值、魔法值、位置等属性。玩家可以存档和读档。
定义备忘录
// 备忘录:保存角色状态
public class RoleStateMemento {
private int hp;
private int mp;
private int x;
private int y;
public RoleStateMemento(int hp, int mp, int x, int y) {
this.hp = hp;
this.mp = mp;
this.x = x;
this.y = y;
}
// 只提供getter,不提供setter,保证只有原发器可以设置
public int getHp() { return hp; }
public int getMp() { return mp; }
public int getX() { return x; }
public int getY() { return y; }
}
定义原发器(游戏角色)
// 原发器:游戏角色
public class GameRole {
private int hp;
private int mp;
private int x;
private int y;
public GameRole(int hp, int mp, int x, int y) {
this.hp = hp;
this.mp = mp;
this.x = x;
this.y = y;
}
// 战斗等方法会改变状态
public void fight() {
hp -= 20;
mp -= 10;
System.out.println("战斗后:hp=" + hp + ", mp=" + mp);
}
public void move(int dx, int dy) {
x += dx;
y += dy;
System.out.println("移动到 (" + x + "," + y + ")");
}
// 保存当前状态到备忘录
public RoleStateMemento saveState() {
return new RoleStateMemento(hp, mp, x, y);
}
// 从备忘录恢复状态
public void restoreState(RoleStateMemento memento) {
this.hp = memento.getHp();
this.mp = memento.getMp();
this.x = memento.getX();
this.y = memento.getY();
System.out.println("恢复状态:hp=" + hp + ", mp=" + mp + ", 位置=(" + x + "," + y + ")");
}
public void display() {
System.out.println("当前状态:hp=" + hp + ", mp=" + mp + ", 位置=(" + x + "," + y + ")");
}
}
定义负责人(存档管理器)
// 负责人:负责保存备忘录,但不能修改备忘录内容
public class Caretaker {
// 可以保存多个存档,用栈实现撤销功能
private Stack<RoleStateMemento> mementoStack = new Stack<>();
// 存档:压入栈
public void saveMemento(RoleStateMemento memento) {
mementoStack.push(memento);
System.out.println("存档成功,当前存档数量:" + mementoStack.size());
}
// 读档:弹出最近一次存档
public RoleStateMemento loadMemento() {
if (mementoStack.isEmpty()) {
System.out.println("没有存档!");
return null;
}
return mementoStack.pop();
}
}
客户端使用
public class GameClient {
public static void main(String[] args) {
// 创建角色
GameRole role = new GameRole(100, 50, 0, 0);
Caretaker caretaker = new Caretaker();
// 初始状态
role.display();
// 第一次存档
caretaker.saveMemento(role.saveState());
// 进行一些操作
role.fight();
role.move(5, 3);
// 第二次存档
caretaker.saveMemento(role.saveState());
// 继续操作
role.fight();
role.move(-2, 1);
// 想回到第二次存档之后的状态(即撤销一次)
System.out.println("\n准备读档...");
RoleStateMemento memento = caretaker.loadMemento();
if (memento != null) {
role.restoreState(memento);
}
role.display();
}
}
输出示例:
当前状态:hp=100, mp=50, 位置=(0,0)
存档成功,当前存档数量:1
战斗后:hp=80, mp=40
移动到 (5,3)
存档成功,当前存档数量:2
战斗后:hp=60, mp=30
移动到 (3,4)
准备读档...
恢复状态:hp=80, mp=40, 位置=(5,3)
当前状态:hp=80, mp=40, 位置=(5,3)
10.5 备忘录模式的变体—使用内部类实现封装
上面的例子中,RoleStateMemento 的字段对外部是可见的(虽然只有 getter),但原发器在恢复时直接调用了 getter。如果想要更严格的封装,可以让备忘录作为原发器的内部类,这样原发器可以直接访问其私有字段,而外部完全无法访问。
public class GameRole {
// ... 属性
// 备忘录作为内部类,私有
private class Memento {
private int hp;
private int mp;
private int x;
private int y;
private Memento(int hp, int mp, int x, int y) {
this.hp = hp;
this.mp = mp;
this.x = x;
this.y = y;
}
private int getHp() { return hp; }
// ... 其他getter
}
// 对外提供的备忘录接口(窄接口)
public interface IRoleMemento { } // 空接口,只作为类型标记
public IRoleMemento saveState() {
return new Memento(hp, mp, x, y);
}
public void restoreState(IRoleMemento memento) {
Memento m = (Memento) memento; // 强制转换,只有原发器能访问内部类
this.hp = m.getHp();
this.mp = m.getMp();
this.x = m.getX();
this.y = m.getY();
}
}
这样,负责人 Caretaker 只能持有 IRoleMemento 类型的引用,无法访问其内部数据,保证了封装性。
10.6 优缺点和适用场景
优点:
- 封装性好:备忘录模式把状态的细节封装在备忘录中,外部无法直接访问,符合开闭原则。
- 简化原发器:原发器不需要管理和维护其历史状态,由负责人负责,职责单一。
- 提供撤销/重做机制:结合负责人(如栈)可以轻松实现撤销操作。
缺点:
- 消耗内存:如果原发器状态很大,且频繁保存备忘录,会消耗大量内存。
- 增加复杂度:引入额外的类和接口,可能使代码结构复杂。
- 负责人可能承担过大职责:如果负责人需要管理大量备忘录,可能变得臃肿。
使用场景:
- 需要提供撤销/恢复功能的系统(如文本编辑器、IDE、游戏存档)。
- 需要保存对象在某个时刻的状态,以便在将来恢复。
- 不希望暴露对象内部实现的情况下,保存其状态快照。
十一,解释器模式
11.1 什么是解释器模式?
解释器模式是一种行为设计模式,它定义了一种语言的文法,并建立一个解释器来解释该语言中的句子。简单来说,就是 **“给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子”**。
类比
假设有一台能听懂简单英文句子的机器人,它只能理解“Hello”、“Good morning”这样的简单词汇。需要写一个程序来解析用户输入的句子,然后让机器人做出相应动作。这个解析并执行的过程,就可以用解释器模式来实现。
主要用途
- 当需要解释执行一种简单的语言时(比如配置文件、脚本语言、数学表达式)。
- 当语法比较简单,且效率不是首要考虑因素时。
- 常用于编译器、计算器、正则表达式引擎等。
11.2 解释器模式的核心思想
解释器模式的核心是:将语法规则表示为一个个的类,通过组合这些类来构成复杂的表达式,然后让这些表达式自己去解释执行。
模式中的角色
- 抽象表达式(Abstract Expression):声明一个抽象的解释操作,所有节点都实现这个接口。
- 终结符表达式(Terminal Expression):实现与文法中的终结符相关联的解释操作。比如数字、变量等。
- 非终结符表达式(Nonterminal Expression):文法中的每条规则对应一个非终结符表达式,比如加法、减法等。它一般会包含其他表达式(子节点)。
- 上下文(Context):包含解释器之外的一些全局信息,比如变量的值等(可选)。
- 客户端(Client):构建抽象语法树,调用解释操作。
11.3 代码实现
目标:实现一个能解析简单动态 SQL 的解释器
我们要解析的 SQL 语句类似这样:
SELECT * FROM user
<if test="name != null">
WHERE name = #{name}
</if>
我们先写一个测试类,看看我们想要达到的效果:
public class MiniMyBatisDemo {
public static void main(String[] args) {
// 1. 准备参数(类似于 MyBatis 的参数对象)
Map<String, Object> params = new HashMap<>();
params.put("name", "张三");
// 2. 创建动态 SQL(实际 MyBatis 是在启动时解析 XML 构建的,这里我们手动构建语法树)
// 静态文本节点
SqlNode selectNode = new StaticTextSqlNode("SELECT * FROM user");
// IF 节点:判断 name != null
SqlNode ifNode = new IfSqlNode(
new StaticTextSqlNode(" WHERE name = #{name}"), // 子节点
"name != null" // 测试表达式
);
// 混合节点(按顺序组合多个 SqlNode)
MixedSqlNode mixedNode = new MixedSqlNode(Arrays.asList(selectNode, ifNode));
// 3. 创建上下文,传入参数
DynamicContext context = new DynamicContext(params);
// 4. 执行解释
mixedNode.apply(context);
// 5. 获取最终解析出的 SQL
System.out.println("解析后的 SQL: " + context.getSql());
}
}
我们希望输出:
解析后的 SQL: SELECT * FROM user WHERE name = #{name}
如果参数中 name 为 null,则输出:
解析后的 SQL: SELECT * FROM user
完整代码实现
-
首先定义抽象表达式 - SqlNode 接口
// 抽象表达式 - 相当于 MyBatis 的 SqlNode public interface SqlNode { boolean apply(DynamicContext context); } -
定义上下文 - 保存参数和拼接 SQL
// 上下文 - 存储参数和中间结果 public class DynamicContext { private final Map<String, Object> bindings; // 参数 private final StringBuilder sqlBuilder = new StringBuilder(); // 拼接 SQL public DynamicContext(Map<String, Object> params) { this.bindings = params; } public void appendSql(String sql) { sqlBuilder.append(sql); } public String getSql() { return sqlBuilder.toString(); } public Object getBinding(String key) { return bindings.get(key); } } -
实现终结符表达式 - 静态文本
// 终结符表达式 - 静态文本(不会变化的 SQL 片段) public class StaticTextSqlNode implements SqlNode { private final String text; public StaticTextSqlNode(String text) { this.text = text; } @Override public boolean apply(DynamicContext context) { // 直接拼接静态文本 context.appendSql(text); return true; // 返回 true 表示成功处理 } } -
实现非终结符表达式 - IF 标签
// 非终结符表达式 - IF 标签 public class IfSqlNode implements SqlNode { private final SqlNode contents; // 子节点(IF 标签内的内容) private final String test; // 测试表达式 public IfSqlNode(SqlNode contents, String test) { this.contents = contents; this.test = test; } @Override public boolean apply(DynamicContext context) { // 简化版的表达式求值(实际 MyBatis 使用 OGNL) if (evaluateBoolean(context, test)) { // 如果条件成立,递归调用子节点的 apply 方法 contents.apply(context); return true; } return false; } // 简化版的表达式求值 private boolean evaluateBoolean(DynamicContext context, String test) { if ("name != null".equals(test)) { return context.getBinding("name") != null; } return false; } } -
实现混合节点 - 按顺序执行多个子节点
// 混合节点 - 按顺序执行多个 SqlNode(相当于 MyBatis 的 MixedSqlNode) class MixedSqlNode implements SqlNode { private final List<SqlNode> contents; public MixedSqlNode(List<SqlNode> contents) { this.contents = contents; } @Override public boolean apply(DynamicContext context) { // 依次执行所有子节点 for (SqlNode sqlNode : contents) { sqlNode.apply(context); } return true; } } -
完整的测试代码
public class MiniMyBatisDemo { public static void main(String[] args) { // 场景1:name 不为 null System.out.println("=== 场景1:name = '张三' ==="); testDynamicSql("张三"); // 场景2:name 为 null System.out.println("\n=== 场景2:name = null ==="); testDynamicSql(null); } private static void testDynamicSql(String nameValue) { // 准备参数 Map<String, Object> params = new HashMap<>(); if (nameValue != null) { params.put("name", nameValue); } // 构建语法树 // SELECT * FROM user SqlNode selectNode = new StaticTextSqlNode("SELECT * FROM user"); // <if test="name != null"> WHERE name = #{name} </if> SqlNode ifNode = new IfSqlNode( new StaticTextSqlNode(" WHERE name = #{name}"), "name != null" ); // 按顺序组合:先 SELECT,再 IF SqlNode rootNode = new MixedSqlNode(Arrays.asList(selectNode, ifNode)); // 执行解释 DynamicContext context = new DynamicContext(params); rootNode.apply(context); // 输出结果 System.out.println("解析后的 SQL: " + context.getSql()); } } -
运行结果
=== 场景1:name = '张三' === 解析后的 SQL: SELECT * FROM user WHERE name = #{name} === 场景2:name = null === 解析后的 SQL: SELECT * FROM user
11.4 解释器模式的核心
通过这个迷你 MyBatis 的例子,可以清晰地看到:
- 递归组合:
IfSqlNode包含子SqlNode,调用apply时可能触发子节点的apply - 文法规则:每个标签(IF、WHERE 等)就是一个文法规则,对应一个类
- 解释执行:每个节点自己负责"解释"自己,生成 SQL 片段
- 上下文传递:
DynamicContext在各个节点间传递,保存中间结果和参数
如果我们想支持更多的标签,比如:
<foreach collection="list" item="item" open="(" separator="," close=")">→ 可以设计ForeachSqlNode<choose><when test="...">...</when><otherwise>...</otherwise></choose>→ 可以设计ChooseSqlNode
每个新标签就是一个新的非终结符表达式类,这就是解释器模式的易于扩展的特性!
十二,访问者模式
12.1 什么是访问者模式
访问者模式的核心思想是:将数据结构与操作分离,允许你在不改变数据结构的前提下,定义新的操作。
类比:体检中心的 "流动医生"
想象你去体检中心:
- 数据结构:你身体的各个器官(心、肝、肺)——这些是稳定的
- 操作:不同科室的医生(心电图医生、B超医生、抽血护士)——这些是变化的
医生们 "访问" 你的各个器官,执行不同的检查。明天如果新增一个 "核磁共振" 项目,只需要新增一个医生(访问者),不需要改动你的器官(数据结构)。
12.2 访问者模式的核心角色
- Visitor(访问者接口):
Doctor,为每个具体元素类声明一个访问方法 - ConcreteVisitor(具体访问者):
Cardiologist、Hepatologist,实现每个访问方法 - Element(元素接口):
BodyPart,定义接受访问者的方法accept(Visitor) - ConcreteElement(具体元素):
Heart、Liver、Lung,实现accept方法 - ObjectStructure(对象结构):
List<BodyPart>,元素的集合
12.3 代码实现
-
定义 "可被访问" 的接口(器官)
public interface BodyPart { void accept(Doctor visitor); // 接待医生来访 } -
具体的数据结构(各个器官)
// 2. 具体的数据结构(各个器官) public class Heart implements BodyPart { @Override public void accept(Doctor visitor) { visitor.visit(this); // 把自己交给医生 } // 心脏特有的方法 public String getHeartRate() { return "72次/分钟"; } } public class Liver implements BodyPart { @Override public void accept(Doctor visitor) { visitor.visit(this); } // 肝脏特有的方法 public String getEnzymeLevel() { return "正常范围"; } } public class Lung implements BodyPart { @Override public void accept(Doctor visitor) { visitor.visit(this); } // 肺特有的方法 public String getCapacity() { return "肺活量3500ml"; } } -
定义访问者接口(医生)
// 3. 定义访问者接口(医生) public interface Doctor { void visit(Heart heart); // 看心脏 void visit(Liver liver); // 看肝脏 void visit(Lung lung); // 看肺 } -
具体的访问者(不同科室的医生)
// 4. 具体的访问者(不同科室的医生) public class Cardiologist implements Doctor { // 心脏科医生 @Override public void visit(Heart heart) { System.out.println("心脏科医生检查心脏:" + heart.getHeartRate()); } @Override public void visit(Liver liver) { System.out.println("心脏科医生:肝脏我不专业,跳过"); } @Override public void visit(Lung lung) { System.out.println("心脏科医生:肺部我不专业,跳过"); } } public class Hepatologist implements Doctor { // 肝病科医生 @Override public void visit(Heart heart) { System.out.println("肝病科医生:心脏我不看"); } @Override public void visit(Liver liver) { System.out.println("肝病科医生检查肝脏:" + liver.getEnzymeLevel()); } @Override public void visit(Lung lung) { System.out.println("肝病科医生:肺部我不看"); } } -
客户端使用
```java
// 5. 客户端使用
public class HospitalDemo {
public static void main(String[] args) {
// 数据结构:人的各个器官
List<BodyPart> body = Arrays.asList(
new Heart(),
new Liver(),
new Lung()
);
// 不同的访问者(医生)
Doctor cardiologist = new Cardiologist();
Doctor hepatologist = new Hepatologist();
System.out.println("=== 心脏科医生查房 ===");
for (BodyPart part : body) {
part.accept(cardiologist); // 每个器官接待心脏科医生
}
System.out.println("\n=== 肝病科医生查房 ===");
for (BodyPart part : body) {
part.accept(hepatologist); // 每个器官接待肝病科医生
}
}
}
```
6. 运行结果:
```txt
=== 心脏科医生查房 ===
心脏科医生检查心脏:72次/分钟
心脏科医生:肝脏我不专业,跳过
心脏科医生:肺部我不专业,跳过
=== 肝病科医生查房 ===
肝病科医生:心脏我不看
肝病科医生检查肝脏:正常范围
肝病科医生:肺部我不看
```
12.5 文件系统操作
// 文件系统的元素
public interface FileElement {
void accept(FileVisitor visitor);
}
public class TextFile implements FileElement {
public void accept(FileVisitor visitor) { visitor.visit(this); }
public void read() { System.out.println("读取文本文件"); }
}
public class ImageFile implements FileElement {
public void accept(FileVisitor visitor) { visitor.visit(this); }
public void display() { System.out.println("显示图片"); }
}
// 文件操作访问者
public interface FileVisitor {
void visit(TextFile file);
void visit(ImageFile file);
}
public class CompressVisitor implements FileVisitor { // 压缩操作
public void visit(TextFile file) {
System.out.println("压缩文本文件");
file.read(); // 先读再压缩
}
public void visit(ImageFile file) {
System.out.println("压缩图片文件");
file.display(); // 先显示再压缩?
}
}
public class VirusScanVisitor implements FileVisitor { // 病毒扫描
public void visit(TextFile file) { System.out.println("扫描文本文件病毒"); }
public void visit(ImageFile file) { System.out.println("扫描图片文件病毒"); }
}
12.6 双重分派
什么是 "单分派"(Single Dispatch)?
-
Java、C++ 等语言默认是单分派语言:
// 单分派:只根据调用者的实际类型决定 class Animal { void makeSound() { System.out.println("动物叫声"); } } class Dog extends Animal { void makeSound() { System.out.println("汪汪"); } } Animal a = new Dog(); a.makeSound(); // 输出"汪汪" 多态生效了,这是"单分派" -
但是当参数也有多态时,问题来了
// 看看这个例子 class Feeder { void feed(Animal a) { System.out.println("喂动物"); } void feed(Dog d) { System.out.println("喂狗"); } } Animal myDog = new Dog(); Feeder feeder = new Feeder(); // 你期望调用 feed(Dog) 版本,但实际... feeder.feed(myDog); // 输出"喂动物" 而不是"喂狗"为什么?因为 Java 是单分派:
- 调用者的实际类型会动态确定(多态)
- 但参数的类型是静态确定的(编译时看声明类型)
访问者模式如何实现 "双重分派"?
// 第一次分派:根据元素的实际类型
part.accept(doctor); // part 是 Heart → Heart.accept()
// part 是 Liver → Liver.accept()
// 第二次分派:在 accept 内部,根据访问者的实际类型
// 在 Heart.accept() 中:
void accept(Doctor doctor) {
// 这里的 this 明确是 Heart 类型
doctor.visit(this); // → 调用 visit(Heart heart)
}
这就是 "双重分派":方法的调用既取决于元素的实际类型,又取决于访问者的实际类型。
重点
- 为什么要双重分派?
- Java 默认只支持单分派(根据调用者动态,参数静态)
- 但我们想让参数也动态
- 访问者模式如何实现?
- 第一重:用
accept方法,根据元素的类型调用不同的方法 - 第二重:在
accept方法内部,用this明确类型,再调用访问者的方法
- 第一重:用
- 关键技巧:
accept(this)中的this不是Animal类型,而是具体的Dog/Cat类型!- 这样编译器就能找到正确的方法重载
双重分派的本质
第一重:选对象(决定谁被操作)
dog.accept(feeder)→ 进入Dog的 accept 方法cat.accept(feeder)→ 进入Cat的 accept 方法
第二重:选方法(决定怎么操作这个对象)
- 在
Dog.accept()里:feeder.feed(this)→ 调用feed(Dog)方法 - 在
Cat.accept()里:feeder.feed(this)→ 调用feed(Cat)方法
12.7 什么时候用访问者模式?
适合的场景:
- 数据结构稳定,操作频繁变化:比如编译器(AST稳定,各种优化操作变化)
- 需要对不同元素执行不同操作:比如文件处理(各种格式文件,各种操作)
- 操作涉及多个元素类,不想污染元素类:保持元素类纯净
现实应用:
- 编译器:抽象语法树(AST) + 各种操作(类型检查、优化、代码生成)
- Spring:
BeanDefinitionVisitor遍历 bean 定义 - ASM字节码框架:
ClassVisitor访问类的各个部分 - 设计工具:图形元素 + 各种操作(导出、渲染、计算面积)