网站Logo 苏叶的belog

行为模式

wdadwa
4
2026-02-20

一,行为模式介绍

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-elseswitch 来区分不同的行为时,就可以用策略模式来代替它。

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自动注入

  1. 定义策略接口

    public interface PromotionStrategy {
        String getType();       // 每个策略要能标识自己的类型
        void execute();         // 执行逻辑
    }
    
  2. 编写具体策略实现类

    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 启动时会自动扫描并实例化它们。

  3. 上下文类(自动收集所有策略)

    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 名(默认类名首字母小写)。

  4. 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 通用策略注册框架模板

  1. 项目结构

    com.example.strategy
    │
    ├── annotation
    │   └── StrategyType.java         // 自定义注解
    │
    ├── core
    │   ├── Strategy.java              // 策略接口
    │   ├── StrategyFactory.java       // 策略注册工厂
    │   └── StrategyExecutor.java      // 统一执行器
    │
    ├── impl
    │   ├── DiscountStrategy.java      // 折扣策略
    │   ├── CashbackStrategy.java      // 返现策略
    │   └── FullReductionStrategy.java // 满减策略
    │
    └── controller
        └── StrategyController.java    // 测试入口
    
  2. 定义注解(标识策略类型)

    package com.example.strategy.annotation;
    
    import java.lang.annotation.*;
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface StrategyType {
        String value(); // 策略类型唯一标识
    }
    
  3. 策略接口

    package com.example.strategy.core;
    
    public interface Strategy {
        /**
         * 执行策略逻辑
         */
        void execute();
    }
    
  4. 具体策略实现类

    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("执行【满减】策略");
        }
    }
    
  5. 核心:策略注册工厂(自动扫描 + 注册)

    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 中。

  6. 策略执行器(统一入口)

    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();
        }
    }
    
  7. 控制器测试

    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)常用于模板里面通用的方法

假设我们有一个“泡茶和冲咖啡”的流程:

  1. 烧水
  2. 冲泡饮料(茶叶 or 咖啡粉)
  3. 倒入杯中
  4. 加调料(柠檬 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 数据库访问模板举例

假设你有一个数据库操作流程:

  1. 连接数据库
  2. 执行SQL
  3. 处理结果
  4. 关闭连接

这些步骤的顺序固定,只是执行的 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 报表生成模板举例

报表导出时流程常见为:

  1. 获取数据
  2. 生成文件内容
  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 中的两种实现方式

  1. 手写结构实现:灵活、清晰,可自定义逻辑。

  2. 使用 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. 请假 1 天 → 直接主管审批
  2. 请假 3 天 → 部门经理审批
  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 MVCFilter 链 / Interceptor 链
NettyChannelPipeline 责任链
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注入搭配责任链

  1. 定义一个通用接口

    public interface Handler<T> {
        boolean handle(T context);
    }
    
  2. 定义一个责任链管理器

    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; // 中断链
            }
        }
    }
    
  3. 定义多个 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;
        }
    }
    
  4. 定义上下文对象

    public class OrderContext {
        private int quantity;
        private int stock;
        private double amount;
    
        // 构造、get/set
    }
    
  5. 使用时注入调用

    @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 控制执行顺序

    • 可随时新增、删除节点,无需改主流程

    • 逻辑清晰:每个责任专注一件事(符合单一职责)

  6. 结合 @OrderOrdered 控制执行顺序

    @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();
    }
}

问题来了:

  1. 如果想添加空调、电视、音响等设备,必须修改遥控器类
  2. 遥控器和具体设备(灯)紧密耦合
  3. 无法实现"撤销"功能
  4. 无法实现"宏命令"(一键同时开多个设备)

这就是命令模式要解决的问题!

6.2 命令模式的核心思想

命令模式 = 把 "请求" 封装成对象

把 "开灯" 这个操作,不直接调用 Light.on(),而是创建一个 "开灯命令对象",这个对象知道如何开灯。

四个核心角色:

  1. 命令接口 - 所有命令都实现这个接口
  2. 具体命令 - 执行具体的操作
  3. 接收者 - 真正干活的对象(灯、空调等)
  4. 调用者 - 发出命令的对象(遥控器)

6.3 具体实现

  1. 创建命令接口

    // 所有命令都实现这个接口
    public interface Command {
        void execute();
        
        void undo();
    }
    
  2. 创建接收者(真正干活的设备)

    // 灯 - 接收者
    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);
        }
    }
    
  3. 创建具体命令

    // 开灯命令
    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);
        }
    }
    
  4. 创建调用者(遥控器)

    // 智能遥控器 - 调用者
    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() {}
    }
    
  5. 宏命令(一次执行多个命令)

    // 宏命令:包含一组命令
    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 命令模式的优缺点

优点

  1. 解耦:调用者和接收者完全解耦
  2. 扩展性:增加新命令不需要修改现有代码
  3. 可组合:可以将多个命令组合成宏命令
  4. 可撤销:轻松实现撤销/重做功能
  5. 队列:可以将命令排队,实现延迟执行

缺点

  1. 类爆炸:每个操作都要创建一个命令类
  2. 学习曲线:比直接调用更复杂

6.5 实际应用场景

  1. GUI按钮操作:每个按钮对应一个命令
  2. 数据库事务:每个操作可提交、回滚
  3. 任务调度:将命令放入队列,按顺序执行
  4. 日志系统:记录命令以便恢复系统状态
  5. 网络请求:将请求封装成命令发送

总结

命令模式的核心本质:将请求封装成对象

  • 命令是对象,不是方法
  • 调用者不知道谁在执行
  • 执行者不知道谁在调用
  • 方便撤销和排队
命令模式 = 调用者 → 【中间商(命令对象)】 → 执行者
                 ↑_________________|
                 中间商赚的差价:
                 1. 存储历史记录
                 2. 支持回退
                 3. 延迟执行
                 4. 排队执行

6.6 实际业务场景举例

业务需求

  1. 用户下单后,如果30分钟内未支付,系统自动取消订单
  2. 取消时要:
    • 恢复商品库存
    • 返还优惠券
    • 发送取消通知给用户
  3. 支持手动取消(用户主动取消)
  4. 支持批量取消(管理员清退异常订单)

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) {
        // 又复制一遍...代码开始失控
    }
}

痛点:

  1. 代码重复:三个取消方法逻辑基本一致
  2. 违背开闭原则:想增加"取消时返还积分"要改3个地方
  3. 难以测试:取消逻辑和定时任务耦合
  4. 无法复用:取消逻辑无法单独复用
  5. 无法追溯:不知道谁取消了订单,为什么取消

6.6.2 用命令模式重构

  1. 创建命令接口

    // 所有取消命令都实现这个接口
    public interface OrderCancelCommand {
        void execute();
        void undo();  // 万一取消错了?支持恢复订单
        String getOrderNo();
        String getCancelSource();  // 取消来源:超时/用户/管理员
    }
    
  2. 创建接收者(各个业务服务)

    // 这些@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);
        }
    }
    
  3. 创建具体的取消命令

    @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;
        }
    }
    
  4. 创建调用者(命令执行器)

    @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()
                ));
        }
    }
    
  5. 在业务层使用

    @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());
            }
        }
    }
    
  6. 控制层提供撤销功能

    @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 优缺点

优点

  1. 简化遍历:客户端无需了解集合内部结构,只需使用统一方法。
  2. 支持多种遍历:可以为同一个集合提供不同迭代器(如正向、反向、过滤迭代器)。
  3. 封装性好:集合内部数据可以安全地被隐藏,迭代器负责遍历逻辑。
  4. 符合开闭原则:增加新的迭代器无需修改集合类。

缺点

  1. 增加类数目:每个集合都需要对应的迭代器类,可能增加系统复杂度。
  2. 对于简单集合可能过度设计:如只需遍历数组,直接 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(中介者接口):声明一个用于与各同事对象通信的方法,通常是一个 notifysend 方法。
  • 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 中介者模式的优缺点

优点

  1. 降低耦合:将对象之间的多对多关系转变为一对多关系,每个对象只与中介者交互,易于理解和管理。
  2. 集中控制交互:交互逻辑集中在中介者中,便于修改和维护,避免将逻辑分散在各个对象中。
  3. 简化对象协议:用一对多的交互替代多对多的网状结构,使对象更简洁。
  4. 提高可复用性:同事类可以独立变化和复用,无需关心其他同事。

缺点

  1. 中介者可能变得庞大复杂:如果系统中同事类很多,交互逻辑复杂,中介者可能变成“上帝对象”,难以维护。
  2. 中介者替换困难:中介者封装了所有交互逻辑,如果需要改变交互方式,可能需要修改中介者类,影响整个系统。

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 优缺点和适用场景

优点

  1. 封装性好:备忘录模式把状态的细节封装在备忘录中,外部无法直接访问,符合开闭原则。
  2. 简化原发器:原发器不需要管理和维护其历史状态,由负责人负责,职责单一。
  3. 提供撤销/重做机制:结合负责人(如栈)可以轻松实现撤销操作。

缺点

  1. 消耗内存:如果原发器状态很大,且频繁保存备忘录,会消耗大量内存。
  2. 增加复杂度:引入额外的类和接口,可能使代码结构复杂。
  3. 负责人可能承担过大职责:如果负责人需要管理大量备忘录,可能变得臃肿。

使用场景:

  • 需要提供撤销/恢复功能的系统(如文本编辑器、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

完整代码实现

  1. 首先定义抽象表达式 - SqlNode 接口

    // 抽象表达式 - 相当于 MyBatis 的 SqlNode
    public interface SqlNode {
        boolean apply(DynamicContext context);
    }
    
  2. 定义上下文 - 保存参数和拼接 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);
        }
    }
    
  3. 实现终结符表达式 - 静态文本

    // 终结符表达式 - 静态文本(不会变化的 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 表示成功处理
        }
    }
    
  4. 实现非终结符表达式 - 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;
        }
    }
    
  5. 实现混合节点 - 按顺序执行多个子节点

    // 混合节点 - 按顺序执行多个 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;
        }
    }
    
  6. 完整的测试代码

    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());
        }
    }
    
  7. 运行结果

    === 场景1:name = '张三' ===
    解析后的 SQL: SELECT * FROM user WHERE name = #{name}
    
    === 场景2:name = null ===
    解析后的 SQL: SELECT * FROM user
    

11.4 解释器模式的核心

通过这个迷你 MyBatis 的例子,可以清晰地看到:

  1. 递归组合IfSqlNode 包含子 SqlNode,调用 apply 时可能触发子节点的 apply
  2. 文法规则:每个标签(IF、WHERE 等)就是一个文法规则,对应一个类
  3. 解释执行:每个节点自己负责"解释"自己,生成 SQL 片段
  4. 上下文传递DynamicContext 在各个节点间传递,保存中间结果和参数

如果我们想支持更多的标签,比如:

  • <foreach collection="list" item="item" open="(" separator="," close=")"> → 可以设计 ForeachSqlNode
  • <choose><when test="...">...</when><otherwise>...</otherwise></choose> → 可以设计 ChooseSqlNode

每个新标签就是一个新的非终结符表达式类,这就是解释器模式的易于扩展的特性!

十二,访问者模式

12.1 什么是访问者模式

访问者模式的核心思想是:将数据结构与操作分离,允许你在不改变数据结构的前提下,定义新的操作。

类比:体检中心的 "流动医生"

想象你去体检中心:

  • 数据结构:你身体的各个器官(心、肝、肺)——这些是稳定的
  • 操作:不同科室的医生(心电图医生、B超医生、抽血护士)——这些是变化的

医生们 "访问" 你的各个器官,执行不同的检查。明天如果新增一个 "核磁共振" 项目,只需要新增一个医生(访问者),不需要改动你的器官(数据结构)。

12.2 访问者模式的核心角色

  1. Visitor(访问者接口)Doctor,为每个具体元素类声明一个访问方法
  2. ConcreteVisitor(具体访问者)CardiologistHepatologist,实现每个访问方法
  3. Element(元素接口)BodyPart,定义接受访问者的方法 accept(Visitor)
  4. ConcreteElement(具体元素)HeartLiverLung,实现 accept 方法
  5. ObjectStructure(对象结构)List<BodyPart>,元素的集合

12.3 代码实现

  1. 定义 "可被访问" 的接口(器官)

    public interface BodyPart {
        void accept(Doctor visitor);  // 接待医生来访
    }
    
    
  2. 具体的数据结构(各个器官)

    // 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. 定义访问者接口(医生)

    // 3. 定义访问者接口(医生)
    public interface Doctor {
        void visit(Heart heart);    // 看心脏
        void visit(Liver liver);    // 看肝脏
        void visit(Lung lung);      // 看肺
    }
    
    
  4. 具体的访问者(不同科室的医生)

    // 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("肝病科医生:肺部我不看");
        }
    }
    
    
  5. 客户端使用

 ```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)
}

这就是 "双重分派":方法的调用既取决于元素的实际类型,又取决于访问者的实际类型

重点

  1. 为什么要双重分派?
    • Java 默认只支持单分派(根据调用者动态,参数静态)
    • 但我们想让参数也动态
  2. 访问者模式如何实现?
    • 第一重:用 accept 方法,根据元素的类型调用不同的方法
    • 第二重:在 accept 方法内部,用 this 明确类型,再调用访问者的方法
  3. 关键技巧
    • 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 什么时候用访问者模式?

适合的场景:

  1. 数据结构稳定,操作频繁变化:比如编译器(AST稳定,各种优化操作变化)
  2. 需要对不同元素执行不同操作:比如文件处理(各种格式文件,各种操作)
  3. 操作涉及多个元素类,不想污染元素类:保持元素类纯净

现实应用:

  • 编译器:抽象语法树(AST) + 各种操作(类型检查、优化、代码生成)
  • SpringBeanDefinitionVisitor 遍历 bean 定义
  • ASM字节码框架ClassVisitor 访问类的各个部分
  • 设计工具:图形元素 + 各种操作(导出、渲染、计算面积)
动物装饰