行为型模式-全景分析

5 阅读51分钟

概述

行为型设计模式是 GoF 二十三种设计模式中数量最庞大的一类,共十一种。它们共同关注一个核心命题:对象之间的责任分配与算法封装,将变化隔离在行为维度。如果说创建型模式解决的是“对象如何诞生”,结构型模式解决的是“对象如何组合”,那么行为型模式回答的则是“对象如何协作”——它描绘了运行时对象间的消息流动与控制传递。

本文将从四个层次构建行为型模式的知识体系闭环:

第一层:核心分类速览——我将十一种模式划分为三大阵营:(1)父子类关系阵营:模板方法、策略、状态,它们通过继承或多态来封装可变的算法步骤;(2)对象间通信阵营:观察者、中介者、责任链、命令,它们关注消息的发送者与接收者之间的解耦与灵活组织;(3)算法与数据结构操作阵营:迭代器、访问者、备忘录、解释器,它们处理对复杂聚合对象的遍历、操作快照与文法解析。

第二层:对比辨析与演进脉络——通过15组易混淆组合的深度对比矩阵和一张多维分布轴线图,彻底厘清相似模式之间的本质差异。随后以演进路线图的形式,揭示从硬编码到设计模式的抽象升级路径。

第三层:框架整合与分布式综合应用——深入 Spring 源码,拆解行为型模式在 Spring 事件机制、Spring MVC 请求链路、事务管理以及 Spring StateMachine 中的协同工作方式。更进一步,将视角拉升至分布式微服务场景,分析行为型模式在工作流引擎、消息驱动架构、API 网关、分布式协调服务中的实战落地,并提供一个“分布式审批工作流系统”的综合案例。

第四层:选型决策与避坑指南——提供面向实际工程的选型决策树与量化参考指标,并系统梳理典型滥用场景与重构建议。最后以20道专家级面试题收尾,融合 Java 8 函数式编程、Spring 生态与分布式架构视角,真正实现从理论到实践的知识闭环。

本文篇幅逾两万字,力求成为行为型设计模式领域的“收官之作”。让我们开始这场深度之旅。


一、十一种行为型模式核心对比

1.1 总览对比表

模式名称核心意图(一句话)解决的核心痛点关键角色数量类间关系特征JDK中的典型应用Spring框架中的体现
模板方法定义算法骨架,将可变步骤延迟到子类多个子类重复编写相似的算法结构2(抽象类、具体类)继承InputStream.read()AbstractList.get()JdbcTemplateRestTemplate
策略定义算法族并使其可互相替换多重条件判断导致类膨胀与耦合3(上下文、策略接口、具体策略)组合ComparatorThreadPoolExecutor.RejectedExecutionHandlerResource 接口的不同实现加载策略
状态对象行为随内部状态改变而改变复杂的状态转换逻辑充斥条件语句3(上下文、状态接口、具体状态)组合JSF 生命周期、Iterator 实现状态机Spring StateMachine
观察者定义一对多的依赖,当一对象状态变化时自动通知所有依赖者对象间紧密耦合的轮询或直接调用4(主题、具体主题、观察者、具体观察者)组合Observer/ObservableEventListenerApplicationEvent/ApplicationListener
中介者用一个中介对象封装一系列对象的交互多对多网状通信的混乱与强耦合3-4(中介者、具体中介者、同事类)组合java.util.Timer(内部队列协调)Spring MVC DispatcherServlet 作为前端控制器中介
责任链使多个对象都有机会处理请求,解耦发送者与接收者固定顺序的请求处理无法动态调整3(处理器接口、具体处理器、客户端)组合FilterLogger 的层级处理HandlerInterceptor、Security Filter Chain
命令将请求封装为对象,支持参数化、排队与撤销动作请求者与执行者强耦合,无法记录历史4(调用者、命令接口、具体命令、接收者)组合RunnableAction(Swing)JdbcTemplate 中的 StatementCallback
迭代器提供统一接口顺序访问聚合对象的元素暴露集合内部结构导致的耦合与脆弱性4(聚合接口、具体聚合、迭代器接口、具体迭代器)组合IteratorListIteratorSpring 的 CompositeIterator
访问者将作用于某数据结构中各元素的操作封装起来,在不改变元素类的前提下定义新操作数据结构稳定但操作频繁变化时,修改元素类违反开闭原则5(访问者接口、具体访问者、元素接口、具体元素、对象结构)组合/双分派FileVisitor(NIO)Spring 的 BeanDefinitionVisitor
备忘录在不破坏封装的前提下捕获并外部化对象的内部状态直接暴露内部状态实现撤销会破坏封装3(发起人、备忘录、负责人)组合/嵌套类java.util.Date(内部毫秒值)Spring Web Flow 的 State 快照
解释器给定一种语言,定义其文法表示及解释器为特定领域简单文法频繁解析构建专用引擎4+(抽象表达式、终结符、非终结符、上下文)组合Pattern(正则表达式引擎)Spring Expression Language(SpEL)

1.2 表格深度解读

上表揭示了行为型模式之间的本质差异。尽管它们在代码结构上有时相似(例如策略与状态在类图上几乎一致),但其意图应用边界截然不同。

从封装变化的角度看

  • 策略模式封装的是可互换的算法族,客户端主动选择策略。
  • 状态模式封装的是依赖于内部状态的行为,状态变迁由上下文或状态自身控制,客户端通常不直接指定状态。
  • 模板方法模式封装的是算法骨架,变化发生在特定的子类钩子方法中,是一种“反向控制”的继承复用。

从对象通信的复杂度看

  • 当系统只有一对多的通知需求时,观察者模式通过显式的订阅列表即可优雅解决。
  • 当多个同事类之间呈现复杂的网状调用时,中介者模式通过引入一个协调中心将多对多转换为星型结构,降低耦合但可能产生“上帝类”。
  • 当请求需要沿着一条或多条路径传播,且每个节点可以决定是否处理或转发时,责任链模式提供了极佳的灵活性,代价是请求可能被漏掉。

从数据结构与算法的分离看

  • 迭代器模式是遍历行为的抽象,它关心的是“如何遍历”,而非“遍历时做什么”。
  • 访问者模式则是操作行为的抽象,它关心的是“遍历时做什么”,依赖于稳定的数据结构。二者结合(如在 AST 遍历中)可达到高度解耦。

从复用粒度看

  • 解释器模式复用的是语法规则,适用于小规模文法解析,在大规模复杂文法下会导致类爆炸。
  • 备忘录模式复用的是对象状态的快照机制,它与命令模式结合是实现高可靠性撤销/恢复的不二法门。

这些对比揭示了行为型模式设计的核心原则:优先使用对象组合而非类继承。除了模板方法和解释器模式(部分)依赖继承,其余九种模式均基于组合构建,这正是它们能够灵活应对复杂行为变化的根本原因。


二、两两对比辨析矩阵

2.1 策略模式 vs 状态模式

意图辨析
策略模式将可互换的算法封装成独立的策略类,由客户端主动选择并注入到上下文对象中。状态模式则封装了依赖于对象内部状态的行为,状态对象自行决定下一状态,客户端通常不直接干预状态切换逻辑。

代码对比

// ========== 策略模式 ==========
// 支付场景:用户选择支付方式,上下文执行相应算法
interface PayStrategy {
    void pay(double amount);
}

class AliPayStrategy implements PayStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("支付宝支付:" + amount);
    }
}

class WechatPayStrategy implements PayStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("微信支付:" + amount);
    }
}

class PaymentContext {
    private PayStrategy strategy;
    
    // 客户端显式设置策略
    public void setStrategy(PayStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void executePayment(double amount) {
        strategy.pay(amount);
    }
}

// 客户端调用
PaymentContext ctx = new PaymentContext();
ctx.setStrategy(new AliPayStrategy());  // 用户主动选择支付宝
ctx.executePayment(100.0);

// ========== 状态模式 ==========
// 订单状态流转:待支付→已支付→已发货,状态自动变迁
interface OrderState {
    void handle(OrderContext ctx);
}

class PendingPaymentState implements OrderState {
    @Override
    public void handle(OrderContext ctx) {
        System.out.println("订单待支付,模拟支付成功...");
        // 状态自动切换,客户端不知道下一个状态是什么
        ctx.setState(new PaidState());
    }
}

class PaidState implements OrderState {
    @Override
    public void handle(OrderContext ctx) {
        System.out.println("订单已支付,进入发货流程...");
        ctx.setState(new ShippedState());
    }
}

class ShippedState implements OrderState {
    @Override
    public void handle(OrderContext ctx) {
        System.out.println("订单已发货,流程结束");
    }
}

class OrderContext {
    private OrderState currentState;
    
    public OrderContext() {
        currentState = new PendingPaymentState(); // 初始状态
    }
    
    public void setState(OrderState state) {
        this.currentState = state;
    }
    
    public void process() {
        currentState.handle(this); // 状态自行处理并切换
    }
}

// 客户端调用
OrderContext order = new OrderContext();
order.process();  // 待支付 -> 已支付
order.process();  // 已支付 -> 已发货
order.process();  // 已发货 -> 结束

核心差异总结

  • 控制权归属:策略模式的控制权在客户端(主动选择),状态模式的控制权在状态类自身(被动触发)。
  • 状态感知:策略模式中上下文通常不知道策略的细节,只是执行;状态模式中上下文明确持有当前状态引用,状态类也持有对上下文的引用以实现切换。
  • 生命周期:策略对象一般无状态,可共享;状态对象可能持有特定状态下的数据。

2.2 观察者模式 vs 发布-订阅模式

意图辨析
观察者模式中,主题(Subject)和观察者(Observer)通过接口直接交互,耦合度较低但仍存在直接依赖。发布-订阅模式则引入了事件通道(Event Channel/Broker),发布者和订阅者完全解耦,彼此不知道对方的存在。

代码对比

// ========== 观察者模式(经典实现) ==========
interface Observer {
    void update(String message);
}

class ConcreteObserver implements Observer {
    private String name;
    public ConcreteObserver(String name) { this.name = name; }
    @Override
    public void update(String message) {
        System.out.println(name + " 收到消息: " + message);
    }
}

class Subject {
    private List<Observer> observers = new ArrayList<>();
    
    public void attach(Observer o) {
        observers.add(o);
    }
    
    public void detach(Observer o) {
        observers.remove(o);
    }
    
    public void notifyObservers(String msg) {
        for (Observer o : observers) {
            o.update(msg);
        }
    }
}

// 使用
Subject subject = new Subject();
subject.attach(new ConcreteObserver("观察者A"));
subject.attach(new ConcreteObserver("观察者B"));
subject.notifyObservers("Hello");

// ========== 发布-订阅模式(基于事件总线) ==========
// 事件类
class OrderCreatedEvent {
    private String orderId;
    public OrderCreatedEvent(String orderId) { this.orderId = orderId; }
    public String getOrderId() { return orderId; }
}

// 事件总线(中介者/通道)
class EventBus {
    private Map<Class<?>, List<Consumer<Object>>> subscribers = new HashMap<>();
    
    public <T> void subscribe(Class<T> eventType, Consumer<T> handler) {
        subscribers.computeIfAbsent(eventType, k -> new ArrayList<>())
                   .add((Consumer<Object>) handler);
    }
    
    public <T> void publish(T event) {
        List<Consumer<Object>> handlers = subscribers.get(event.getClass());
        if (handlers != null) {
            handlers.forEach(h -> h.accept(event));
        }
    }
}

// 使用
EventBus bus = new EventBus();
bus.subscribe(OrderCreatedEvent.class, e -> System.out.println("处理订单创建:" + e.getOrderId()));
bus.subscribe(OrderCreatedEvent.class, e -> System.out.println("发送通知:" + e.getOrderId()));

// 发布者只依赖EventBus,不知道谁在订阅
bus.publish(new OrderCreatedEvent("ORD-123"));

核心差异总结

  • 耦合程度:观察者模式是松耦合(观察者接口依赖),发布-订阅是零耦合(发布者只依赖消息通道)。
  • 通信范围:观察者模式通常在单个JVM内同步进行;发布-订阅模式可跨进程、跨网络,天然支持异步。
  • 典型实现java.util.Observer 是观察者模式;Kafka/RabbitMQ/Guava EventBus 是发布-订阅模式。

2.3 责任链模式 vs 装饰器模式

意图辨析
责任链模式中的每个处理器可以终止请求传递,形成一条“可能被截断的流水线”。装饰器模式则强调功能增强,装饰器必须将请求传递给被装饰对象,以保证核心功能不丢失。

代码对比

// ========== 责任链模式 ==========
interface Handler {
    void setNext(Handler next);
    void handle(Request request);
}

abstract class AbstractHandler implements Handler {
    protected Handler next;
    @Override
    public void setNext(Handler next) { this.next = next; }
    protected void next(Request request) {
        if (next != null) next.handle(request);
    }
}

class AuthHandler extends AbstractHandler {
    @Override
    public void handle(Request request) {
        if (request.isAuthenticated()) {
            System.out.println("认证通过,传递给下一节点");
            next(request);  // 继续传递
        } else {
            System.out.println("认证失败,终止链");
            // 不调用next,链在此中断
        }
    }
}

class LoggingHandler extends AbstractHandler {
    @Override
    public void handle(Request request) {
        System.out.println("记录请求日志");
        next(request); // 必须调用next,否则后续逻辑丢失
    }
}

// ========== 装饰器模式 ==========
interface Component {
    void operation();
}

class ConcreteComponent implements Component {
    @Override
    public void operation() {
        System.out.println("核心业务逻辑");
    }
}

abstract class Decorator implements Component {
    protected Component wrapped;
    public Decorator(Component c) { this.wrapped = c; }
    @Override
    public void operation() {
        wrapped.operation(); // 必须调用,否则核心功能丢失
    }
}

class LoggingDecorator extends Decorator {
    public LoggingDecorator(Component c) { super(c); }
    @Override
    public void operation() {
        System.out.println("日志记录:before");
        super.operation();  // 必须调用
        System.out.println("日志记录:after");
    }
}

// 使用
Component component = new LoggingDecorator(new ConcreteComponent());
component.operation(); // 日志+核心业务,都执行

核心差异总结

  • 请求传递义务:责任链中节点可选传递;装饰器中装饰器必须传递。
  • 功能关注点:责任链关注“是否处理以及如何处理”;装饰器关注“如何增强原有功能”。
  • 组合方式:责任链是线性的节点链表;装饰器是层层嵌套的包装结构。

2.4 命令模式 vs 策略模式

意图辨析
命令模式将动作请求本身封装为对象,强调行为参数化、支持撤销、日志等;策略模式封装的是算法的不同实现,强调同一接口下的互换性。

代码对比

// ========== 命令模式 ==========
// 编辑器操作,支持撤销
interface Command {
    void execute();
    void undo();
}

class Editor {
    private String content = "";
    public void insert(String text) { content += text; }
    public void deleteLast(int length) { 
        content = content.substring(0, content.length() - length); 
    }
    public String getContent() { return content; }
}

class InsertCommand implements Command {
    private Editor editor;
    private String text;
    
    public InsertCommand(Editor editor, String text) {
        this.editor = editor;
        this.text = text;
    }
    
    @Override
    public void execute() {
        editor.insert(text);
    }
    
    @Override
    public void undo() {
        editor.deleteLast(text.length());
    }
}

class CommandInvoker {
    private Stack<Command> history = new Stack<>();
    
    public void executeCommand(Command cmd) {
        cmd.execute();
        history.push(cmd);
    }
    
    public void undo() {
        if (!history.isEmpty()) {
            history.pop().undo();
        }
    }
}

// ========== 策略模式 ==========
// 压缩算法选择
interface CompressionStrategy {
    byte[] compress(byte[] data);
}

class GzipCompression implements CompressionStrategy {
    @Override
    public byte[] compress(byte[] data) {
        System.out.println("使用GZIP压缩");
        return data; // 模拟
    }
}

class ZipCompression implements CompressionStrategy {
    @Override
    public byte[] compress(byte[] data) {
        System.out.println("使用ZIP压缩");
        return data;
    }
}

class Compressor {
    private CompressionStrategy strategy;
    
    public void setStrategy(CompressionStrategy s) { this.strategy = s; }
    
    public byte[] compress(byte[] data) {
        return strategy.compress(data);
    }
}

核心差异总结

  • 对象职责:命令对象封装“做什么”(操作+接收者+参数);策略对象封装“怎么做”(算法细节)。
  • 附加能力:命令天然支持队列、日志、撤销/重做;策略不支持。
  • 调用者角色:命令有专门的Invoker;策略由客户端直接调用或注入。

2.5 模板方法模式 vs 策略模式

意图辨析
模板方法通过继承固定算法骨架,子类只能扩展特定步骤(钩子方法)。策略模式通过组合允许运行时替换整个算法。

代码对比

// ========== 模板方法模式 ==========
abstract class DataExporter {
    // 模板方法,固定导出流程
    public final void export() {
        connect();
        String data = fetchData();
        String formatted = formatData(data);
        writeData(formatted);
        disconnect();
    }
    
    protected void connect() { System.out.println("建立连接"); }
    protected abstract String fetchData();  // 子类实现
    protected abstract String formatData(String data); // 子类实现
    protected void writeData(String data) { System.out.println("写入数据: " + data); }
    protected void disconnect() { System.out.println("断开连接"); }
}

class XMLExporter extends DataExporter {
    @Override
    protected String fetchData() { return "raw xml data"; }
    @Override
    protected String formatData(String data) { return "<xml>" + data + "</xml>"; }
}

class JSONExporter extends DataExporter {
    @Override
    protected String fetchData() { return "raw json data"; }
    @Override
    protected String formatData(String data) { return "{data:" + data + "}"; }
}

// ========== 策略模式 ==========
interface FormatStrategy {
    String format(String data);
}

class XMLFormat implements FormatStrategy {
    @Override
    public String format(String data) {
        return "<xml>" + data + "</xml>";
    }
}

class JSONFormat implements FormatStrategy {
    @Override
    public String format(String data) {
        return "{data:" + data + "}";
    }
}

class FlexibleExporter {
    private FormatStrategy formatStrategy;
    
    public FlexibleExporter(FormatStrategy s) { this.formatStrategy = s; }
    public void setFormatStrategy(FormatStrategy s) { this.formatStrategy = s; }
    
    public void export() {
        connect();
        String data = fetchData();
        String formatted = formatStrategy.format(data); // 策略替换
        writeData(formatted);
        disconnect();
    }
    
    private void connect() { System.out.println("建立连接"); }
    private String fetchData() { return "raw data"; }
    private void writeData(String data) { System.out.println("写入: " + data); }
    private void disconnect() { System.out.println("断开连接"); }
}

核心差异总结

  • 复用方式:模板方法复用算法骨架;策略模式复用算法整体
  • 扩展时机:模板方法在编译期通过继承确定;策略模式在运行期通过组合动态改变。
  • 代码粒度:模板方法适合流程步骤固定、少量步骤变化;策略模式适合整个算法族可互换。

2.6 访问者模式 vs 迭代器模式

意图辨析
迭代器模式提供一种统一的方式遍历聚合对象中的元素,而不暴露其内部表示。访问者模式则用于对聚合对象中的元素执行某种操作,且操作可独立于元素类变化。二者常配合使用:迭代器负责遍历,访问者负责操作。

代码对比

// ========== 迭代器模式 ==========
interface Iterator<T> {
    boolean hasNext();
    T next();
}

interface Aggregate<T> {
    Iterator<T> createIterator();
}

class ConcreteAggregate<T> implements Aggregate<T> {
    private T[] items;
    public ConcreteAggregate(T[] items) { this.items = items; }
    
    @Override
    public Iterator<T> createIterator() {
        return new ConcreteIterator();
    }
    
    private class ConcreteIterator implements Iterator<T> {
        private int position = 0;
        @Override
        public boolean hasNext() { return position < items.length; }
        @Override
        public T next() { return items[position++]; }
    }
}

// ========== 访问者模式 ==========
interface Element {
    void accept(Visitor visitor);
}

class Book implements Element {
    private String title;
    private double price;
    public Book(String title, double price) { this.title = title; this.price = price; }
    public String getTitle() { return title; }
    public double getPrice() { return price; }
    
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

class Fruit implements Element {
    private String name;
    private double weight;
    public Fruit(String name, double weight) { this.name = name; this.weight = weight; }
    public String getName() { return name; }
    public double getWeight() { return weight; }
    
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

interface Visitor {
    void visit(Book book);
    void visit(Fruit fruit);
}

class PriceCalculatorVisitor implements Visitor {
    private double total = 0;
    @Override
    public void visit(Book book) { total += book.getPrice(); }
    @Override
    public void visit(Fruit fruit) { total += fruit.getWeight() * 10; }
    public double getTotal() { return total; }
}

// ========== 二者协同 ==========
List<Element> cart = Arrays.asList(new Book("设计模式", 59.0), new Fruit("苹果", 2.5));
PriceCalculatorVisitor visitor = new PriceCalculatorVisitor();

// 使用迭代器遍历
Iterator<Element> it = cart.iterator();
while (it.hasNext()) {
    it.next().accept(visitor);
}
System.out.println("总价: " + visitor.getTotal());

// Java 8 简化
cart.forEach(e -> e.accept(visitor));

核心差异总结

  • 关注点:迭代器关注“如何遍历”,访问者关注“遍历时执行什么操作”。
  • 扩展性:新增遍历算法(如前序、后序)应修改迭代器;新增元素操作应新增访问者。
  • 依赖关系:迭代器独立于元素类型;访问者依赖具体元素类型(每个元素有一个visit重载)。

2.7 中介者模式 vs 观察者模式

意图辨析
观察者模式用于一对多的通知广播,主题对象通知所有注册的观察者。中介者模式用于多对多的复杂交互协调,同事对象不再直接通信,而是通过中介者转发。

代码对比

// ========== 观察者模式:温度传感器广播 ==========
interface TemperatureObserver {
    void update(float temp);
}

class TemperatureSensor {
    private List<TemperatureObserver> observers = new ArrayList<>();
    private float currentTemp;
    
    public void addObserver(TemperatureObserver o) { observers.add(o); }
    
    public void setTemperature(float temp) {
        this.currentTemp = temp;
        notifyObservers();
    }
    
    private void notifyObservers() {
        for (TemperatureObserver o : observers) {
            o.update(currentTemp);
        }
    }
}

// ========== 中介者模式:聊天室协调 ==========
interface ChatMediator {
    void sendMessage(String msg, User user);
    void addUser(User user);
}

class ChatRoom implements ChatMediator {
    private List<User> users = new ArrayList<>();
    
    @Override
    public void addUser(User user) { users.add(user); }
    
    @Override
    public void sendMessage(String msg, User sender) {
        for (User u : users) {
            if (u != sender) {
                u.receive(msg, sender.getName());
            }
        }
    }
}

abstract class User {
    protected ChatMediator mediator;
    protected String name;
    
    public User(ChatMediator med, String name) {
        this.mediator = med;
        this.name = name;
    }
    
    public abstract void send(String msg);
    public abstract void receive(String msg, String senderName);
    public String getName() { return name; }
}

class ChatUser extends User {
    public ChatUser(ChatMediator med, String name) { super(med, name); }
    
    @Override
    public void send(String msg) {
        System.out.println(name + " 发送: " + msg);
        mediator.sendMessage(msg, this);
    }
    
    @Override
    public void receive(String msg, String senderName) {
        System.out.println(name + " 收到来自 " + senderName + " 的消息: " + msg);
    }
}

核心差异总结

  • 交互拓扑:观察者是星型广播(Subject为中心);中介者是星型协调(Mediator为中心),但同事之间不直接交互。
  • 职责范围:观察者模式中Subject只负责通知,不关心观察者间的协作;中介者模式中Mediator负责协调多个同事类的交互逻辑。
  • 复杂度:观察者模式在简单一对多场景下足够;当同事类之间存在复杂相互调用时,中介者能有效降低耦合。

2.8 备忘录模式 vs 命令模式(撤销场景)

意图辨析
备忘录模式负责保存和恢复对象状态;命令模式负责封装操作请求。在撤销场景中,命令对象通常持有备忘录来记录执行前的状态,从而实现撤销。

代码对比

// ========== 备忘录模式核心 ==========
class EditorMemento {
    private final String content; // 不可变快照
    
    public EditorMemento(String content) { this.content = content; }
    public String getContent() { return content; }
}

class EditorOriginator {
    private String content = "";
    
    public void write(String text) { content += text; }
    public String getContent() { return content; }
    
    public EditorMemento save() { return new EditorMemento(content); }
    public void restore(EditorMemento memento) { 
        this.content = memento.getContent(); 
    }
}

// ========== 命令模式 + 备忘录实现撤销 ==========
interface UndoableCommand {
    void execute();
    void undo();
}

class WriteCommand implements UndoableCommand {
    private EditorOriginator editor;
    private String textToWrite;
    private EditorMemento backup;  // 命令持有备忘录
    
    public WriteCommand(EditorOriginator editor, String text) {
        this.editor = editor;
        this.textToWrite = text;
    }
    
    @Override
    public void execute() {
        backup = editor.save();     // 执行前保存状态
        editor.write(textToWrite);
    }
    
    @Override
    public void undo() {
        editor.restore(backup);     // 恢复之前的状态
    }
}

class CommandHistory {
    private Stack<UndoableCommand> history = new Stack<>();
    
    public void execute(UndoableCommand cmd) {
        cmd.execute();
        history.push(cmd);
    }
    
    public void undo() {
        if (!history.isEmpty()) {
            history.pop().undo();
        }
    }
}

// 使用
EditorOriginator editor = new EditorOriginator();
CommandHistory history = new CommandHistory();

history.execute(new WriteCommand(editor, "Hello "));
history.execute(new WriteCommand(editor, "World!"));
System.out.println(editor.getContent()); // "Hello World!"
history.undo();
System.out.println(editor.getContent()); // "Hello "

核心差异总结

  • 职责分离:备忘录只关心状态存储,命令关心操作执行。
  • 撤销实现:备忘录可独立用于撤销(手动保存/恢复);命令模式通过组合备忘录将撤销自动化。
  • 封装性:备忘录确保状态保存不破坏原对象的封装,命令则将撤销逻辑集中管理。

2.9 解释器模式 vs 组合模式

意图辨析
解释器模式用于定义一种语言的文法表示,并基于该表示解释执行句子。组合模式用于将对象组织成树形结构以表示“整体-部分”层次。解释器模式通常使用组合模式构建抽象语法树(AST),但核心关注点在文法解析与执行。

代码对比

// ========== 组合模式构建树结构 ==========
interface Component {
    void operation();
}

class Leaf implements Component {
    private String name;
    public Leaf(String name) { this.name = name; }
    @Override
    public void operation() { System.out.println("Leaf: " + name); }
}

class Composite implements Component {
    private List<Component> children = new ArrayList<>();
    public void add(Component c) { children.add(c); }
    @Override
    public void operation() {
        System.out.println("Composite:");
        children.forEach(Component::operation);
    }
}

// ========== 解释器模式(利用组合构建AST) ==========
interface Expression {
    int interpret(Context ctx);
}

class NumberExpression implements Expression {
    private int value;
    public NumberExpression(int v) { this.value = v; }
    @Override
    public int interpret(Context ctx) { return value; }
}

class AddExpression implements Expression {
    private Expression left, right; // 组合结构
    
    public AddExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }
    
    @Override
    public int interpret(Context ctx) {
        return left.interpret(ctx) + right.interpret(ctx);
    }
}

class SubtractExpression implements Expression {
    private Expression left, right;
    
    public SubtractExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }
    
    @Override
    public int interpret(Context ctx) {
        return left.interpret(ctx) - right.interpret(ctx);
    }
}

class Context {
    private Map<String, Integer> variables = new HashMap<>();
    public void assign(String var, int value) { variables.put(var, value); }
    public int lookup(String var) { return variables.getOrDefault(var, 0); }
}

// 构建表达式: a + (b - 3)
Context ctx = new Context();
ctx.assign("a", 10);
ctx.assign("b", 8);

Expression expr = new AddExpression(
    new NumberExpression(ctx.lookup("a")),
    new SubtractExpression(
        new NumberExpression(ctx.lookup("b")),
        new NumberExpression(3)
    )
);
System.out.println(expr.interpret(ctx)); // 10 + (8-3) = 15

核心差异总结

  • 模式目的:组合模式是结构型模式,解决对象组织问题;解释器模式是行为型模式,解决语言解析执行问题。
  • 树的使用:解释器使用组合树表示文法结构,但每个节点具备解释能力;组合模式中的节点只需具备通用操作。
  • 复杂度:组合模式相对简单;解释器模式涉及文法定义、终结符/非终结符、上下文等,复杂度更高。

2.10 状态模式 vs 有限状态机

意图辨析
有限状态机(FSM)是一种数学模型,描述系统在有限状态间的转移规则。状态模式是面向对象设计模式,通过将每个状态封装为独立类来实现状态机逻辑,提高可维护性和扩展性。

代码对比

// ========== 简单FSM实现(if-else / switch) ==========
class TrafficLightFSM {
    enum State { RED, GREEN, YELLOW }
    private State current = State.RED;
    
    public void change() {
        switch (current) {
            case RED:
                current = State.GREEN;
                break;
            case GREEN:
                current = State.YELLOW;
                break;
            case YELLOW:
                current = State.RED;
                break;
        }
        System.out.println("当前灯色: " + current);
    }
}

// ========== 状态模式实现FSM ==========
interface TrafficLightState {
    void handle(TrafficLightContext context);
}

class RedState implements TrafficLightState {
    @Override
    public void handle(TrafficLightContext context) {
        System.out.println("红灯亮,等待60秒...");
        // 状态类决定下一状态
        context.setState(new GreenState());
    }
}

class GreenState implements TrafficLightState {
    @Override
    public void handle(TrafficLightContext context) {
        System.out.println("绿灯亮,通行");
        context.setState(new YellowState());
    }
}

class YellowState implements TrafficLightState {
    @Override
    public void handle(TrafficLightContext context) {
        System.out.println("黄灯亮,请注意");
        context.setState(new RedState());
    }
}

class TrafficLightContext {
    private TrafficLightState currentState;
    
    public TrafficLightContext() {
        currentState = new RedState(); // 初始状态
    }
    
    public void setState(TrafficLightState state) {
        this.currentState = state;
    }
    
    public void change() {
        currentState.handle(this);
    }
}

核心差异总结

  • 扩展性:状态模式通过新增状态类扩展,符合开闭原则;简单FSM需要修改switch分支。
  • 复杂度管理:状态模式将每个状态的行为和数据封装在一起,避免大型条件判断;对于状态少且简单的场景,简单FSM更直接。
  • 典型应用:Spring StateMachine是状态模式的工业级实现;枚举+switch是FSM的朴素实现。

2.11 访问者模式 vs 策略模式

意图辨析
访问者模式处理的是稳定的数据结构上多变的操作,涉及双分派。策略模式处理的是同一接口下可替换的算法,仅涉及单分派。

代码对比

// ========== 访问者模式:汽车部件检查 ==========
interface CarElement {
    void accept(CarElementVisitor visitor);
}

class Engine implements CarElement {
    public void accept(CarElementVisitor visitor) { visitor.visit(this); }
    public void start() { System.out.println("引擎启动"); }
}

class Wheel implements CarElement {
    private int position;
    public Wheel(int pos) { this.position = pos; }
    public void accept(CarElementVisitor visitor) { visitor.visit(this); }
    public int getPosition() { return position; }
}

interface CarElementVisitor {
    void visit(Engine engine);
    void visit(Wheel wheel);
}

class PrintVisitor implements CarElementVisitor {
    @Override
    public void visit(Engine e) { System.out.println("访问引擎"); e.start(); }
    @Override
    public void visit(Wheel w) { System.out.println("访问轮子: " + w.getPosition()); }
}

class CheckVisitor implements CarElementVisitor {
    @Override
    public void visit(Engine e) { System.out.println("检查引擎..."); }
    @Override
    public void visit(Wheel w) { System.out.println("检查轮子气压..."); }
}

// ========== 策略模式:汽车启动策略 ==========
interface StartStrategy {
    void start();
}

class NormalStart implements StartStrategy {
    @Override
    public void start() { System.out.println("普通启动"); }
}

class SportStart implements StartStrategy {
    @Override
    public void start() { System.out.println("运动模式启动,提高转速"); }
}

class Car {
    private StartStrategy startStrategy;
    private List<CarElement> elements = Arrays.asList(new Engine(), new Wheel(1));
    
    public void setStartStrategy(StartStrategy s) { this.startStrategy = s; }
    
    public void start() {
        startStrategy.start(); // 策略执行
    }
    
    public void accept(CarElementVisitor visitor) {
        elements.forEach(e -> e.accept(visitor)); // 访问者遍历
    }
}

核心差异总结

  • 数据结构依赖:访问者依赖于稳定的元素类型层次;策略不依赖数据结构。
  • 分派机制:访问者使用双分派(element.accept(visitor)visitor.visit(this));策略是单分派。
  • 扩展方向:访问者易于增加新操作(新Visitor),难增加新元素;策略易于增加新算法实现。

2.12 责任链模式 vs 中介者模式

意图辨析
责任链是线性传递结构,请求沿链传播,每个节点可决定是否处理。中介者是星型调度结构,同事类之间不直接通信,全部通过中介者协调。

代码对比

// ========== 责任链模式:审批流程 ==========
abstract class Approver {
    protected Approver next;
    public void setNext(Approver next) { this.next = next; }
    public abstract void approve(int amount);
}

class Manager extends Approver {
    @Override
    public void approve(int amount) {
        if (amount <= 1000) {
            System.out.println("经理审批通过: " + amount);
        } else if (next != null) {
            System.out.println("经理无权审批,转交上级");
            next.approve(amount);
        }
    }
}

class Director extends Approver {
    @Override
    public void approve(int amount) {
        if (amount <= 5000) {
            System.out.println("总监审批通过: " + amount);
        } else if (next != null) {
            System.out.println("总监无权审批,转交上级");
            next.approve(amount);
        }
    }
}

// ========== 中介者模式:UI组件协调 ==========
interface DialogMediator {
    void notify(Component sender, String event);
}

class LoginDialog implements DialogMediator {
    private Button okButton;
    private CheckBox rememberCheck;
    private TextField usernameField;
    
    public void setOkButton(Button btn) { this.okButton = btn; }
    public void setRememberCheck(CheckBox cb) { this.rememberCheck = cb; }
    public void setUsernameField(TextField f) { this.usernameField = f; }
    
    @Override
    public void notify(Component sender, String event) {
        if (sender == rememberCheck && event.equals("check")) {
            if (rememberCheck.isChecked()) {
                usernameField.setText("上次登录用户");
            }
        }
        if (sender == okButton && event.equals("click")) {
            if (!usernameField.getText().isEmpty()) {
                System.out.println("登录成功");
            }
        }
    }
}

abstract class Component {
    protected DialogMediator mediator;
    public Component(DialogMediator m) { this.mediator = m; }
}

class Button extends Component {
    public Button(DialogMediator m) { super(m); }
    public void click() { mediator.notify(this, "click"); }
}

class CheckBox extends Component {
    private boolean checked;
    public CheckBox(DialogMediator m) { super(m); }
    public void check() { checked = !checked; mediator.notify(this, "check"); }
    public boolean isChecked() { return checked; }
}

核心差异总结

  • 拓扑结构:责任链是线性的;中介者是星型的。
  • 通信方式:责任链中节点知道下一节点;中介者中同事类只知道中介者。
  • 适用场景:责任链适合多级审批、过滤器链;中介者适合GUI组件交互、聊天室。

2.13 模板方法模式 vs 建造者模式

意图辨析
模板方法是行为型模式,定义算法执行步骤的顺序。建造者是创建型模式,用于分步骤构建复杂对象,且构建步骤的调用顺序可由指挥者灵活控制。

代码对比

// ========== 模板方法模式 ==========
abstract class ReportGenerator {
    // 固定步骤
    public final void generate() {
        fetchData();
        processData();
        formatReport();
        outputReport();
    }
    protected abstract void fetchData();
    protected abstract void processData();
    protected abstract void formatReport();
    protected void outputReport() { System.out.println("输出报告"); }
}

class PDFReportGenerator extends ReportGenerator {
    @Override protected void fetchData() { System.out.println("获取PDF数据"); }
    @Override protected void processData() { System.out.println("处理PDF数据"); }
    @Override protected void formatReport() { System.out.println("格式化PDF"); }
}

// ========== 建造者模式 ==========
class Computer {
    private String cpu;
    private String ram;
    private String disk;
    // getters/setters...
    
    public static class Builder {
        private Computer computer = new Computer();
        public Builder cpu(String cpu) { computer.cpu = cpu; return this; }
        public Builder ram(String ram) { computer.ram = ram; return this; }
        public Builder disk(String disk) { computer.disk = disk; return this; }
        public Computer build() { return computer; }
    }
}

// 使用建造者,调用顺序自由
Computer pc = new Computer.Builder()
    .cpu("i7")
    .disk("1TB SSD")
    .ram("16GB")
    .build();

核心差异总结

  • 模式类别:模板方法是行为型,建造者是创建型。
  • 控制重点:模板方法控制行为流程;建造者控制对象构建流程
  • 顺序约束:模板方法步骤顺序固定;建造者中调用者可自由决定调用顺序。

2.14 迭代器模式 vs 访问者模式

说明:此对比已在2.6中详细阐述,此处不再重复。


2.15 命令模式 vs 备忘录模式

说明:此对比已在2.8中详细阐述,此处不再重复。


2.16 行为型模式三维分布轴线图

flowchart TB
    subgraph Axis1[行为封装维度]
        direction LR
        S1[策略] --> E1[封装算法族]
        S2[模板方法] --> E2[封装算法骨架]
        S3[状态] --> E3[封装状态行为]
        S4[命令] --> E4[封装请求对象]
        S5[备忘录] --> E5[封装状态快照]
    end
    
    subgraph Axis2[对象通信维度]
        direction LR
        O1[观察者] --> F1[一对多通知]
        O2[中介者] --> F2[多对多协调]
        O3[责任链] --> F3[线性传递处理]
    end
    
    subgraph Axis3[算法与数据结构维度]
        direction LR
        I1[迭代器] --> G1[遍历抽象]
        I2[访问者] --> G2[操作扩展]
        I3[解释器] --> G3[文法解析执行]
    end
    
    Axis1 <--> Axis2
    Axis2 <--> Axis3
    Axis3 <--> Axis1
    
    style Axis1 fill:#e1f5fe
    style Axis2 fill:#fff3e0
    style Axis3 fill:#e8f5e9

上图将十一种行为型设计模式按照其核心关注点划分为三个维度,并以三轴交叉的形式展示它们之间的内在联系。

第一维度:行为封装
该维度包含策略、模板方法、状态、命令、备忘录五种模式。它们的共同特征是将变化的行为逻辑从主流程中剥离,封装为独立的对象或方法。策略模式封装可替换的算法族,模板方法封装固定的算法骨架,状态模式封装依赖内部状态的行为,命令模式将请求封装为可传递的对象,备忘录模式则封装对象内部状态的快照。这五种模式体现了“将变化隔离在行为维度”的设计哲学。

第二维度:对象通信
观察者、中介者、责任链三种模式专注于解决对象之间的消息传递与协调问题。观察者模式处理一对多的依赖通知,中介者模式将多对多的网状通信简化为星型结构,责任链模式则通过线性链表解耦请求的发送者与接收者。它们共同构建了松耦合的对象协作网络。

第三维度:算法与数据结构
迭代器、访问者、解释器三种模式聚焦于复杂数据结构的操作与解析。迭代器模式提供统一的遍历接口,隐藏集合内部实现;访问者模式将作用于数据结构上的一系列操作封装到独立的访问者对象中;解释器模式则更进一步,为特定文法构建抽象语法树并解释执行。

三个维度并非孤立存在。图中用双向箭头表示模式间的交叉与协同关系:例如访问者模式常依赖迭代器进行遍历,解释器模式使用组合模式(结构型)构建AST,命令模式常与备忘录模式配合实现撤销。理解这一三维分布,有助于开发者从问题本质出发快速锁定候选模式,并为模式组合提供理论依据。


三、行为型模式的演进路线图

3.1 算法封装演进线

flowchart LR
    subgraph "起点"
        A["硬编码 if-else / switch"]
    end
    
    subgraph "第一阶段"
        B["策略模式"] -->|"推动力: 消除条件分支 支持动态切换"| B1["算法族独立变化"]
    end
    
    subgraph "第二阶段"
        C["状态模式"] -->|"推动力: 行为随状态自动变迁 避免状态判断蔓延"| C1["状态驱动行为"]
    end
    
    subgraph "第三阶段"
        D["模板方法模式"] -->|"推动力: 固定算法骨架 复用控制流程"| D1["反向控制 钩子扩展"]
    end
    
    subgraph "第四阶段"
        E["访问者模式"] -->|"推动力: 数据结构稳定但操作多变 双分派解耦"| E1["操作与结构彻底分离"]
    end

    A --> B
    B --> C
    C --> D
    D --> E

文字说明:算法封装的演进始于对条件分支的厌恶。最初,我们用if-else判断类型来执行不同逻辑,随着分支膨胀,策略模式通过多态将算法抽取为独立类,实现了算法的即插即用。然而当对象的行为不仅取决于外部选择,更依赖于内部状态变迁时,状态模式进一步将状态机逻辑分布到各状态类中,上下文无需维护复杂的状态判断。当多个子类共享相同算法骨架仅部分步骤不同时,模板方法模式提供了一种复用控制流的优雅方案,通过钩子方法将变化点延迟到子类。

3.2 对象通信演进线

flowchart LR
    subgraph "起点"
        A["直接调用/硬引用"]
    end
    
    subgraph "第一阶段"
        B["观察者模式"] -->|"推动力: 一对多自动通知 解除主动轮询"| B1["主题-观察者松耦合"]
    end
    
    subgraph "第二阶段"
        C["中介者模式"] -->|"推动力: 多对多网状通信集中管理 减少连线数量"| C1["星型协调架构"]
    end
    
    subgraph "第三阶段"
        D["责任链模式"] -->|"推动力: 请求处理流程动态编排 发送者无需知道接收者"| D1["线性/树状过滤器链"]
    end
    
    subgraph "第四阶段"
        E["命令模式"] -->|"推动力: 请求对象化 支持异步排队日志与撤销"| E1["行为参数化与历史回放"]
    end

    A --> B
    B --> C
    C --> D
    D --> E

文字说明:对象通信的演进始于最原始的直接方法调用。当系统出现一对多的状态变化通知需求时,观察者模式引入了订阅机制,使得主题对象无需关心有哪些观察者,观察者也不必轮询。随着协作对象增多,形成复杂的网状调用图时,中介者模式通过引入调度中心将所有交互收敛为星型结构,大幅降低了系统的认知负载。当请求需要穿过一系列处理者,并且处理顺序需要灵活配置时,责任链模式登场,它将处理者组织成链,每个节点可自由决定是否继续传递。最终,当动作本身也需要被管理(如撤销、重做、定时执行)时,命令模式将“动作调用”提升为第一公民——命令对象,彻底实现了行为参数化,成为任务调度、事务日志与宏录制的基础。

3.3 数据结构操作演进线

flowchart LR
    subgraph "起点"
        A["手动遍历/暴露内部结构"]
    end
    
    subgraph "第一阶段"
        B["迭代器模式"] -->|"推动力: 统一遍历接口 隐藏集合内部实现"| B1["遍历与集合解耦"]
    end
    
    subgraph "第二阶段"
        C["解释器模式"] -->|"推动力: 对特定语言文法进行结构化解析与求值"| C1["构建与遍历AST"]
    end
    
    subgraph "第三阶段"
        D["备忘录模式"] -->|"推动力: 在不破坏封装前提下捕获对象内部状态快照"| D1["状态回滚与恢复"]
    end

    A --> B
    B --> C
    C --> D

文字说明:数据结构操作的演进关注的是如何在不破坏封装的前提下遍历与修改复杂聚合对象。最初,客户端直接依赖集合的get(index)或数组下标,导致遍历逻辑与集合类型强耦合。迭代器模式通过统一的Iterator接口将遍历行为抽象出来,使得同一套遍历算法可应用于不同数据结构。在此基础上,当需要解析并执行一门小语言(如规则表达式)时,解释器模式将文法规则映射为表达式对象,并利用迭代器(或递归)遍历抽象语法树完成求值。而当业务需要将对象状态恢复到历史某一时刻时,备忘录模式提供了一种安全的快照机制,确保在不暴露内部细节的前提下捕获与恢复状态,常用于编辑器、事务回滚等场景。


四、框架源码中的行为型模式协同应用

4.1 Spring 事件机制中的观察者模式与责任链模式

Spring 的事件广播器 ApplicationEventMulticaster 是观察者模式的经典实现。当 ApplicationContext 发布一个事件(如 ContextRefreshedEvent)时,SimpleApplicationEventMulticaster 会遍历所有注册的 ApplicationListener,根据事件类型与泛型信息精确匹配并调用。

public class SimpleApplicationEventMulticaster {
    private final List<ApplicationListener<?>> listeners = new ArrayList<>();
    
    public void multicastEvent(ApplicationEvent event) {
        for (ApplicationListener<?> listener : getApplicationListeners(event)) {
            // 可配置异步执行
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            } else {
                invokeListener(listener, event);
            }
        }
    }
}

责任链的隐性体现:虽然事件广播是广播式而非链式传递,但 Spring 允许通过 @Order 注解或 Ordered 接口控制监听器执行顺序,从而形成一种“有序责任链”的变体。此外,事件在处理过程中可能发布新事件,形成隐式的事件处理链。

4.2 Spring MVC 请求处理链路中的多种行为型模式

sequenceDiagram
    participant C as 客户端
    participant DS as DispatcherServlet
    participant HR as HandlerMapping
    participant HA as HandlerAdapter
    participant HI as HandlerInterceptor(链)
    participant H as Controller(Handler)
    participant VR as ViewResolver

    C->>DS: HTTP请求
    DS->>DS: doService() (模板方法)
    DS->>DS: doDispatch()
    DS->>HR: getHandler()
    HR-->>DS: HandlerExecutionChain
    DS->>HI: applyPreHandle() (责任链)
    alt 前置拦截通过
        DS->>HA: supports() (策略模式)
        HA-->>DS: true
        DS->>HA: handle() (适配器调用)
        HA->>H: invoke Controller method
        H-->>HA: ModelAndView
        HA-->>DS: ModelAndView
        DS->>HI: applyPostHandle() (责任链)
        DS->>VR: resolveViewName() (策略模式)
        VR-->>DS: View对象
        DS->>View: render()
        View-->>C: 响应视图
    else 前置拦截未通过
        DS-->>C: 拦截器直接响应
    end

文字说明:Spring MVC 的 DispatcherServlet 是行为型模式的集大成者。其 servicedoDispatch 方法是典型的模板方法应用,定义了处理 HTTP 请求的标准骨架:查找处理器、执行拦截器前置、调用处理器、执行拦截器后置、解析视图、渲染。HandlerInterceptor 链体现了责任链模式,三个方法(preHandle、postHandle、afterCompletion)分别在请求生命周期的不同节点被调用,任何 preHandle 返回 false 都会打断链条。HandlerAdapter 接口及其实现类(如 RequestMappingHandlerAdapter)采用了策略模式DispatcherServlet 遍历所有适配器,通过 supports(handler) 找到能处理当前 Handler 的适配器,从而解耦了 Servlet 与具体控制器类型(注解控制器、HttpRequestHandler 等)。ViewResolver 同样采用策略模式,根据逻辑视图名解析为具体的 View 实现(JSP、Thymeleaf、JSON 等)。这种多模式协同使得 Spring MVC 具备极高的扩展性,开发者可以轻松替换或增强请求处理的任何环节。

4.3 Spring 事务管理中模板方法模式与策略模式的配合

Spring 的事务管理抽象核心是 PlatformTransactionManager(策略接口)与 TransactionTemplate(模板类)。

// 策略接口
public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition);
    void commit(TransactionStatus status);
    void rollback(TransactionStatus status);
}

// 模板方法模式
public class TransactionTemplate {
    public <T> T execute(TransactionCallback<T> action) {
        TransactionStatus status = this.transactionManager.getTransaction(this);
        try {
            T result = action.doInTransaction(status); // 钩子方法
            this.transactionManager.commit(status);
            return result;
        } catch (RuntimeException | Error ex) {
            rollbackOnException(status, ex);
            throw ex;
        }
    }
}

TransactionTemplateexecute 方法定义了固定的事务处理骨架:开启事务 → 执行业务逻辑 → 提交事务 → 异常回滚。业务逻辑通过回调接口 TransactionCallback 传入,这是模板方法模式与回调机制的结合。而底层具体的事务管理行为(如 JDBC 本地事务、JTA 分布式事务)则通过 PlatformTransactionManager 的不同实现(策略)来完成。这种设计完美体现了“将变化隔离”的原则:事务的流程控制不变,事务的具体实现可变。

4.4 Spring StateMachine 中状态模式与观察者模式的结合

Spring StateMachine 是一个专用于状态机实现的框架,其核心便是状态模式。状态机实例(StateMachine)扮演上下文角色,内部维护当前状态(State)对象。当收到事件时,状态机委托给当前状态对象处理,状态对象根据事件与守卫条件决定是否执行动作(Action)并迁移到目标状态。

观察者模式则体现在状态机的监听器体系上:

stateMachine.addStateListener(new StateMachineListenerAdapter<States, Events>() {
    @Override
    public void stateChanged(State<States, Events> from, State<States, Events> to) {
        log.info("状态从 {} 迁移至 {}", from.getId(), to.getId());
    }
    
    @Override
    public void eventNotAccepted(Message<Events> event) {
        log.warn("事件 {} 未被接受", event.getPayload());
    }
});

当状态发生变更、事件未处理、进入/退出状态等时机,框架会主动回调所有注册的监听器,这是一种典型的观察者模式应用,使得外部组件能够对状态机内部变化做出响应,而无需侵入状态机逻辑本身。


五、行为型模式的选型决策指南

5.1 决策流程图

flowchart TD
    Start["开始选型"] --> Q1{"需要动态替换算法/行为?"}
    Q1 -->|"是"| Q1_1{"行为是否由内部状态驱动?"}
    Q1_1 -->|"是"| Q1_1_1{"状态数量是否多且易变?"}
    Q1_1_1 -->|"是"| State["状态模式"]
    Q1_1_1 -->|"否 简单"| FSM["有限状态机/枚举"]
    Q1_1 -->|"否 由客户端选择"| Strategy["策略模式"]
    
    Q1 -->|"否"| Q2{"是否涉及对象间通信?"}
    Q2 -->|"是"| Q2_1{"是一对多通知?"}
    Q2_1 -->|"是"| Observer["观察者模式"]
    Q2_1 -->|"否"| Q2_2{"是多对多复杂网状通信?"}
    Q2_2 -->|"是"| Mediator["中介者模式"]
    Q2_2 -->|"否"| Q2_3{"请求需沿链传递处理?"}
    Q2_3 -->|"是"| Chain["责任链模式"]
    Q2_3 -->|"否"| Command["命令模式"]
    
    Q2 -->|"否"| Q3{"操作复杂数据结构?"}
    Q3 -->|"是"| Q3_1{"需要统一遍历不同集合?"}
    Q3_1 -->|"是"| Iterator["迭代器模式"]
    Q3_1 -->|"否"| Q3_2{"需要为稳定结构增加新操作?"}
    Q3_2 -->|"是"| Visitor["访问者模式"]
    Q3_2 -->|"否"| Q3_3{"需要解析特定文法?"}
    Q3_3 -->|"是"| Interpreter["解释器模式"]
    Q3_3 -->|"否"| Memento["备忘录模式"]
    
    Q3 -->|"否"| Q4{"需要固定算法骨架?"}
    Q4 -->|"是"| Template["模板方法模式"]
    Q4 -->|"否"| End["可能不需要行为型模式"]

使用说明:此决策树从四个核心问题出发引导选型。第一步判断是否需要动态替换行为,将用户引向策略、状态模式;第二步判断对象通信场景,区分观察者、中介者、责任链与命令;第三步处理数据结构操作需求;第四步处理算法骨架复用。需要注意的是,实际项目中往往多个模式协同工作,本图提供的是核心问题的切入点,选型后还应结合下文量化指标进行复核。

5.2 技术选型量化参考指标

模式关键指标阈值参考
策略模式算法族数量、客户端显式选择频率算法变体 ≥3 且持续增加时引入
状态模式状态数量、状态变迁复杂度状态 ≥5 或迁移规则频繁变化时收益明显
观察者模式主题状态变化频率、观察者数量观察者 ≥3 且通知逻辑不固定时使用
责任链模式处理节点数、链重构频率节点 ≥4 或运行时需动态调整顺序时引入
命令模式需支持的操作种类、撤销/日志需求需支持撤销或宏命令时必须使用
模板方法模式子类重复代码行数、算法步骤固定度重复骨架代码超过50行时重构
迭代器模式集合内部结构复杂度、遍历方式数有2种以上遍历需求或需隐藏复杂结构时
中介者模式同事类数量、交互关系密集度同事类 ≥5 且两两交互超过半数时考虑
备忘录模式需保存的历史状态数量、状态大小需深拷贝或版本回溯时使用
解释器模式文法规则数、解析需求频率规则数 ≤20 且相对稳定时适合,否则用脚本引擎
访问者模式数据结构元素类型数、操作扩展频率元素类型稳定(年变化率<10%)且操作频繁增加时

六、分布式场景下的行为型模式综合应用

6.1 分布式工作流引擎中的状态模式与责任链模式

在分布式工作流引擎(如 Netflix Conductor、Camunda)中,状态模式用于管理流程实例的生命周期状态(如 PENDINGRUNNINGCOMPLETEDFAILEDTIMED_OUT)。每个状态对象包含进入动作、退出动作以及可接受的事件列表。责任链模式则应用于审批节点链:一个审批任务可能依次经过组长、经理、总监、HRBP 审批,每个节点可能同意、拒绝、转交,链的走向由流程定义决定。

6.2 消息驱动架构中的观察者模式与命令模式

在基于消息中间件(Kafka、RabbitMQ、RocketMQ)的微服务架构中,事件发布与订阅天然契合观察者模式。服务 A 发布 OrderCreatedEvent 到 Topic,服务 B、C、D 各自订阅该 Topic,消息中间件充当了分布式 EventBus 的角色。命令模式则体现在命令消息中:例如“扣减库存命令”、“发送优惠券命令”,命令对象序列化后在网络上传输,消费端反序列化并执行。命令模式为异步任务编排、补偿与重试提供了基础。

6.3 分布式协调服务中的中介者模式

ZooKeeper、etcd、Consul 等服务注册中心在微服务架构中扮演着中介者的角色。服务提供者(同事类)启动时向注册中心注册自己的地址,服务消费者(另一个同事类)从注册中心发现地址并调用。提供者与消费者之间不直接耦合,而是通过注册中心进行间接通信。注册中心还承担了健康检查、配置推送等协调职责,这是中介者模式在分布式环境下的宏大展现。

6.4 API 网关中的责任链模式与策略模式

Spring Cloud Gateway、Zuul 等 API 网关的核心是过滤器链(责任链模式)。请求进入网关后,依次通过认证、鉴权、限流、日志、路由转发等一系列过滤器,每个过滤器可决定是否继续传递。路由转发逻辑则采用策略模式:根据请求路径、Header 等信息,从多个路由规则中选择匹配的一条,并将请求转发到对应的后端服务。此外,负载均衡算法(轮询、加权、哈希)也是策略模式的体现。

6.5 分布式任务调度中的模板方法模式

XXL-JOB、ElasticJob 等分布式任务调度框架中,开发者编写的任务处理逻辑只需实现 IJobHandler 接口或继承抽象类,框架则负责任务分片、故障转移、执行日志、调度触发等固定流程。这正是模板方法模式的应用:调度框架定义了任务执行的算法骨架(获取分片参数 → 前置处理 → 执行任务 → 后置处理 → 上报结果),而将变化的核心业务逻辑留给开发者实现。

6.6 分布式规则引擎中的解释器模式与访问者模式

规则引擎(如 Drools、EasyRules)需要对规则脚本进行解析。解析阶段通常使用解释器模式构建抽象语法树(AST),每个语法节点(如 ANDOR、比较运算符)都是一个表达式对象,可解释执行。当需要对 AST 进行优化、分析或生成其他语言代码时,访问者模式便派上用场:在不修改语法节点类的前提下,新增诸如 RuleOptimizationVisitorSQLGenerationVisitor 等操作,实现算法与数据结构的分离。

6.7 综合案例:分布式审批工作流系统设计

6.7.1 系统架构图

flowchart TB
    subgraph 前端
        UI[审批界面]
    end
    
    subgraph 网关层
        GW[API Gateway] --> |过滤器链| Auth[认证过滤器]
        Auth --> Limit[限流过滤器]
        Limit --> Route[路由转发]
    end
    
    subgraph 流程引擎服务
        WE[WorkflowEngine]
        WE --> Parser[流程定义解析器]
        Parser --> |解释器模式| AST[流程AST]
        Parser --> |访问者模式| Validator[校验访问者]
        WE --> SM[状态机管理]
        SM --> |状态模式| StateMachine[流程实例状态机]
        WE --> Chain[审批链管理器]
        Chain --> |责任链| Node1[节点1] --> Node2[节点2] --> Node3[节点3]
    end
    
    subgraph 消息与事件
        MQ[消息队列] --> |观察者模式| Sub1[通知服务]
        MQ --> Sub2[日志服务]
        MQ --> Sub3[监控服务]
    end
    
    subgraph 任务调度
        Scheduler[分布式调度器]
        Scheduler --> |模板方法| Job[超时自动驳回任务]
    end
    
    GW --> WE
    WE --> MQ
    WE --> Scheduler

6.7.2 核心代码骨架

// 1. 流程定义解析(解释器模式 + 访问者模式)
interface WorkflowExpression { void interpret(Context ctx); }
class SequenceExpression implements WorkflowExpression {
    private List<WorkflowExpression> steps;
    public void interpret(Context ctx) { steps.forEach(s -> s.interpret(ctx)); }
}

interface WorkflowVisitor {
    void visit(SequenceExpression seq);
    void visit(ParallelExpression parallel);
}
class ValidationVisitor implements WorkflowVisitor { ... }

// 2. 流程实例状态机(状态模式)
interface WorkflowState {
    void handleEvent(WorkflowInstance instance, WorkflowEvent event);
}
class PendingState implements WorkflowState {
    public void handleEvent(WorkflowInstance instance, WorkflowEvent event) {
        if (event == WorkflowEvent.START) {
            instance.setState(new RunningState());
        }
    }
}

// 3. 审批节点链(责任链模式)
interface ApproveHandler {
    void setNext(ApproveHandler next);
    void handle(ApproveContext ctx);
}
abstract class AbstractApproveHandler implements ApproveHandler {
    protected ApproveHandler next;
    public void handle(ApproveContext ctx) {
        if (doHandle(ctx) && next != null) next.handle(ctx);
    }
    protected abstract boolean doHandle(ApproveContext ctx);
}

// 4. 事件通知(观察者模式)
@EventListener
public void onApprovalPassed(ApprovalPassedEvent event) {
    // 发送邮件、短信、站内信
}

// 5. 超时自动驳回任务(模板方法模式)
@Component
public class TimeoutRejectJob extends AbstractScheduledJob {
    @Override
    protected void executeInternal(JobExecutionContext context) {
        List<WorkflowInstance> instances = findTimeoutInstances();
        instances.forEach(instance -> 
            instance.handleEvent(WorkflowEvent.TIMEOUT)
        );
    }
}

综合案例说明:本案例设计了一个微服务化的分布式审批工作流系统,充分体现了行为型模式在分布式环境下的综合运用。系统入口通过 API 网关的责任链(过滤器链)完成安全与流量控制。流程定义服务使用解释器模式将 JSON/XML 流程定义解析为内存中的 AST,并利用访问者模式进行定义校验、流程图生成等额外操作,避免修改核心 AST 节点类。流程实例运行时,采用状态模式管理生命周期,每个状态类封装了可接受的事件与迁移逻辑,支持挂起、恢复、驳回、转交等复杂操作。审批节点链采用责任链模式,允许运行时动态调整审批顺序(如加签、减签)。关键状态变化(如审批通过、驳回、超时)通过 Spring 事件机制(观察者模式)广播给消息队列,解耦通知、日志、监控等非核心关注点。超时自动处理则借助分布式调度框架的模板方法模式,复用调度框架的任务分片、故障转移能力。此设计将业务复杂度分散到各行为型模式中,使得系统具备高扩展性、高内聚性与低耦合性,能够适应企业级审批流程的多变需求。


七、常见设计误区与反模式警示

7.1 策略模式的滥用

误区现象:为每一种细微变化都创建一个策略类,导致策略类爆炸(如 AddStrategySubStrategyMulStrategy),且策略之间缺乏复用。

重构示例

// 反模式:为每个操作创建策略类
class AddStrategy implements Strategy { ... }
class SubtractStrategy implements Strategy { ... }

// 改进:使用函数式接口或参数化策略
class ArithmeticContext {
    private BinaryOperator<Integer> strategy;
    public void setStrategy(BinaryOperator<Integer> s) { this.strategy = s; }
}
// 使用
context.setStrategy((a, b) -> a + b);
context.setStrategy(Math::addExact);

7.2 观察者模式的滥用

误区现象:观察者与主题之间形成循环依赖;未及时移除观察者导致内存泄漏;单个事件触发大量同步通知导致线程阻塞(通知风暴)。

重构示例

// 内存泄漏:未移除监听器
class LeakySubject {
    private List<Observer> obs = new ArrayList<>();
    // 缺少 removeObserver 调用
}

// 改进:使用 WeakReference 或显式生命周期管理
class SafeSubject {
    private List<WeakReference<Observer>> obs = new ArrayList<>();
}
// 或使用 EventBus 异步模式 + 线程池隔离

7.3 责任链模式的滥用

误区现象:链过长(如超过20个节点),每个节点仅做微小处理,导致请求穿透链的延时不可接受;链中某个节点吞掉异常导致调试困难。

重构示例

// 问题:链太长,性能差
// 改进:将节点分组,使用策略模式+责任链混合
class CompositeHandler implements Handler {
    private List<Handler> group; // 组内并行判断或短路
    public void handle(Request req) {
        group.stream().filter(h -> h.supports(req)).findFirst()
             .ifPresent(h -> h.handle(req));
    }
}

7.4 访问者模式的滥用

误区现象:在元素类频繁增删的场景下强行使用访问者模式,导致每个新元素都要修改访问者接口及所有具体访问者,违反开闭原则。

重构示例

// 当元素类型变化频繁时,改为使用策略模式或简单的类型判断
interface Operation {
    void apply(Element e);
}
class OperationDispatcher {
    private Map<Class<?>, Operation> ops = new HashMap<>();
    public void apply(Element e) {
        ops.get(e.getClass()).apply(e);
    }
}

7.5 中介者模式的滥用

误区现象:中介者类承载了过多业务逻辑,演变为“上帝类”,系统耦合从网状转移为对中介者的单点依赖。

重构示例

// 上帝中介者
class GodMediator {
    void onEvent(Colleague c, Event e) {
        if (c == A && e == X) { B.doSth(); C.doOther(); /* 上千行逻辑 */ }
    }
}
// 改进:引入事件总线或命令模式拆分中介者职责
interface EventHandler { void handle(Event e); }
class Mediator {
    private Map<Class<?>, List<EventHandler>> handlers;
    public void dispatch(Event e) {
        handlers.get(e.getClass()).forEach(h -> h.handle(e));
    }
}

7.6 解释器模式的滥用

误区现象:试图用解释器模式解析 SQL、复杂表达式或通用编程语言,导致表达式类数量爆炸,维护成本极高。

重构示例

// 反模式:手写复杂SQL解释器
// 改进:使用现成的解析器生成器(ANTLR)或脚本引擎(Groovy、MVEL)
ScriptEngine engine = new ScriptEngineManager().getEngineByName("groovy");
Object result = engine.eval("a + b * c");

八、面试题精选与专家级解答

1. 策略模式和状态模式在 UML 类图上几乎一模一样,如何从意图上区分?请各举一个真实场景

解答:策略模式是客户端主动选择不同的算法,状态模式是上下文被动响应内部状态变化而改变行为。

  • 策略场景:电商促销计算(满减、打折、赠品),由运营后台配置规则,前端根据用户选择的优惠类型传入不同策略键。
  • 状态场景:订单状态机(待支付、已支付、已发货、已完成),状态转换由系统事件(支付成功、发货完成)触发,用户不直接选择。

2. 观察者模式和发布-订阅模式常被混淆,它们在架构上有何本质区别?

解答:观察者模式是松耦合(Subject 持有 Observer 列表),发布-订阅是完全解耦(通过中间代理)。

  • 观察者模式:addObserver / notifyObservers,同步调用,适合单机应用内组件通信。
  • 发布-订阅模式:publish / subscribe,通过 EventBus 或消息队列,异步、跨进程,适合微服务间通信。

3. Spring 中 ApplicationEventMulticaster 是如何体现观察者模式的?与 Guava EventBus 有何异同?

解答

  • ApplicationEventMulticaster 是 Spring 事件机制的核心,默认实现 SimpleApplicationEventMulticaster 维护 ListenerRetriever 集合,在 multicastEvent 时遍历监听器并调用。
  • 与 Guava EventBus 异同:
    • 相同:都支持同步/异步事件分发,基于注解 @EventListener / @Subscribe
    • 不同:Spring 事件默认与 ApplicationContext 生命周期绑定,支持事务事件(@TransactionalEventListener);Guava EventBus 更轻量,可独立使用,但无事务感知。

4. 责任链模式在 Servlet Filter、Netty ChannelPipeline、Spring Security FilterChain 中的实现有何异同?

解答

  • Servlet Filter:通过 FilterChain 接口串联,doFilter 内部调用 chain.doFilter 传递,链条由 Web 容器管理。
  • Netty ChannelPipeline:双向链表结构,ChannelHandlerContext 作为上下文触发 fireChannelRead 等方法,支持入站出站分离。
  • Spring Security FilterChain:本质是 Servlet Filter 的代理链,但通过 SecurityFilterChain 匹配不同请求路径选择不同链条,实现多链共存。

5. 命令模式如何与备忘录模式配合实现可撤销操作?请画出时序图

sequenceDiagram
    participant Client
    participant Invoker
    participant Command
    participant Receiver
    participant Caretaker

    Client->>Command: new Command(receiver)
    Client->>Invoker: storeCommand(cmd)
    Client->>Invoker: executeCommand()
    Invoker->>Command: execute()
    Command->>Receiver: action()
    Command->>Command: createMemento()
    Command->>Caretaker: saveMemento(m)
    
    Note over Client,Caretaker: 一段时间后需要撤销
    
    Client->>Invoker: undo()
    Invoker->>Caretaker: getMemento()
    Caretaker-->>Invoker: Memento
    Invoker->>Command: restore(memento)
    Command->>Receiver: undoAction()

6. 模板方法模式与策略模式都可以消除 if-else,它们的适用场景有何区别?

解答:模板方法复用的是流程骨架,策略复用的是算法实现

  • 当多个业务的执行步骤顺序一致,但某个步骤细节不同时,用模板方法(如 JdbcTemplatequery 方法,获取连接、创建语句、执行、释放连接是骨架,解析结果集是可变的)。
  • 当业务逻辑的整个算法可被整体替换时,用策略模式(如支付方式切换:支付宝、微信、银行卡)。

7. 访问者模式被称为“最复杂的设计模式”,它的双重分派机制是如何工作的?为什么说它违背了开闭原则?

解答

  • 双重分派:客户端调用 element.accept(visitor)(第一重分派,根据 element 实际类型选择 accept 方法),在 accept 内部调用 visitor.visit(this)(第二重分派,根据 visitor 实际类型选择重载的 visit 方法)。
  • 违背开闭原则:访问者模式对新增元素类型是封闭的。如果 Element 接口新增一个子类,所有 Visitor 接口及实现类都必须添加对应的 visit 方法,这是其“数据结构稳定”假设下的代价。

8. 解释器模式在实际开发中使用频率较低,但在哪些框架中它是不可或缺的?请举例分析

解答

  • Spring Expression Language (SpEL):解析类似 #{user.name} 的表达式,内部使用解释器模式构建 AST 并求值。
  • Hibernate HQL/JPQL 解析:将 HQL 字符串解析为抽象语法树,再转换为原生 SQL。
  • Drools 规则引擎:将 DRL 规则文件解析为可执行的 Rete 网络。
  • ShardingSphere SQL 解析:将 SQL 语句解析为 AST,然后进行改写、路由。

9. 中介者模式与门面模式都可以简化交互,它们的意图有何不同?

解答

  • 中介者模式:关注多个对象之间的交互解耦,将网状依赖变为星型。同事类依然相互知晓,但通过中介者间接通信。
  • 门面模式:为子系统外部提供一个统一的高层接口,简化客户端的调用。子系统内部组件可以不知晓门面的存在。

10. 迭代器模式在 Java 集合框架中广泛应用,请分析 ArrayList 与 CopyOnWriteArrayList 迭代器的 fail-fast 与 fail-safe 设计

解答

  • ArrayList 迭代器 fail-fast:迭代器内部持有 expectedModCount,每次 next 时检查是否与集合的 modCount 一致,不一致抛出 ConcurrentModificationException,用于及早发现并发修改错误。
  • CopyOnWriteArrayList 迭代器 fail-safe:迭代器创建时拷贝一份当前数组快照,后续遍历的是快照,不会抛出异常,但无法感知迭代期间的修改,适用于读多写少的高并发场景。

11. 状态模式如何与 Spring StateMachine 结合实现复杂状态机?请简述核心组件

解答: Spring StateMachine 核心组件包括:

  • StateMachine:状态机实例,作为上下文。
  • State:状态枚举或状态对象,封装状态行为。
  • Event:触发状态迁移的事件。
  • Transition:迁移规则,包含源状态、目标状态、事件、守卫、动作。
  • Action:状态进入/退出或迁移过程中执行的动作。
  • Guard:条件守卫,决定迁移是否允许。
  • StateMachineListener:监听器接口,观察状态机变化(观察者模式)。

12. 在分布式系统中,如何利用观察者模式实现配置中心的动态刷新?长轮询与事件推送分别对应什么模式?

解答

  • 长轮询:客户端定时(如30s超时)向配置中心发起请求,配置中心持有连接,有变更时立即返回,否则超时后返回304。这属于拉模式(观察者主动检查)。
  • 事件推送:客户端与配置中心建立长连接(WebSocket/gRPC stream),配置中心变更时主动推送事件,属于推模式(经典观察者)。
  • Spring Cloud Config + Bus 使用消息队列实现推送,Nacos 默认使用长轮询。

13. 行为型模式中哪些是类模式(基于继承),哪些是对象模式(基于组合)?这对扩展性有何影响?

解答

  • 类模式:模板方法、解释器(部分)。基于继承,静态绑定,编译时确定行为,扩展性较弱。
  • 对象模式:策略、状态、观察者、中介者、责任链、命令、迭代器、访问者、备忘录。基于组合,运行时动态替换行为,扩展性强,符合合成复用原则。

14. 如何使用 Java 8 的函数式接口简化策略模式、命令模式、观察者模式的实现?

解答

// 策略模式:使用 Function、Predicate、BinaryOperator
Map<String, BinaryOperator<Integer>> strategies = new HashMap<>();
strategies.put("ADD", (a,b) -> a+b);

// 命令模式:使用 Runnable、Consumer、Supplier
List<Runnable> commands = new ArrayList<>();
commands.add(() -> System.out.println("Execute"));

// 观察者模式:使用 Consumer
List<Consumer<Event>> listeners = new ArrayList<>();
listeners.forEach(l -> l.accept(event));

15. 访问者模式与解释器模式在 AST 遍历中如何配合?请以 ShardingSphere 的 SQL 解析为例

解答

  • 解释器模式:ShardingSphere 将 SQL 字符串解析为 SQLStatement 对象树(AST),例如 SelectStatement 包含 FromSegmentWhereSegment 等。
  • 访问者模式SQLASTVisitor 接口定义了遍历 AST 节点的方法,MySQLSelectSQLVisitor 等具体访问者负责处理不同数据库方言的改写与路由逻辑。SQLStatement 节点通过 accept(visitor) 将自己传递给访问者,实现双重分派。这种设计使得新增 SQL 语法支持时,只需新增访问者逻辑,无需修改 AST 节点类。

16. 备忘录模式与事件溯源(Event Sourcing)有何异同?在分布式事务中如何选择?

解答

  • 备忘录模式:保存对象的状态快照,恢复时直接覆盖当前状态。适用于对象状态较小、变更频率不高的场景。
  • 事件溯源:存储所有状态变更事件,恢复时重放事件序列。适用于需要审计、回溯、复杂业务规则的领域模型。
  • 在分布式事务中,事件溯源天然与 CQRS 结合,通过事件日志实现最终一致性;备忘录模式更适用于单体应用内的撤销/恢复功能(如编辑器)。

17. 责任链模式中如何处理某个节点失败后的补偿操作?请给出设计思路

解答: 采用两阶段责任链补偿链

interface CompensableHandler extends Handler {
    void compensate(Request req);
}

class ChainExecutor {
    private List<CompensableHandler> executed = new ArrayList<>();
    public void execute(List<CompensableHandler> chain, Request req) {
        try {
            for (CompensableHandler h : chain) {
                h.handle(req);
                executed.add(h);
            }
        } catch (Exception e) {
            // 逆序执行补偿
            Collections.reverse(executed);
            executed.forEach(h -> h.compensate(req));
            throw e;
        }
    }
}

18. 模板方法模式中的钩子方法与好莱坞原则有什么关系?Spring 中有哪些经典钩子方法?

解答

  • 好莱坞原则:“Don‘t call us, we’ll call you.” 高层组件调用低层组件,而非反之。
  • 模板方法模式完美体现该原则:父类模板方法定义流程,在特定时机回调子类的钩子方法,子类只需填空,无需调用父类流程控制。
  • Spring 经典钩子:AbstractApplicationContext.refresh() 中的 postProcessBeanFactory()onRefresh()finishRefresh()AbstractAutowireCapableBeanFactory 中的 initializeBean() 回调 BeanPostProcessor

19. 如何用行为型模式设计一个支持 AB 测试与灰度发布的微服务网关?

解答

  • 策略模式:定义多种路由选择策略(如 WeightedRouteStrategyHeaderBasedRouteStrategyIpHashStrategy)。
  • 责任链模式:灰度过滤器链:解析请求 Header → 查询灰度规则 → 染色 → 选择策略 → 转发。
  • 观察者模式:当灰度规则配置变更时,通过事件通知网关动态刷新路由表。
  • 命令模式:将路由请求封装为命令,支持重试、降级、熔断。

20. 十一种行为型模式中,哪些模式天然支持分布式场景,哪些需要额外改造?请分类说明

解答

  • 天然支持
    • 观察者模式:借助消息队列实现跨进程事件通知。
    • 命令模式:命令对象可序列化后远程传输,天然适合任务分发。
    • 责任链模式:每个节点可部署在不同服务中,通过 RPC 或消息传递请求。
    • 策略模式:策略实现可远程调用,客户端持有策略键。
  • 需改造
    • 状态模式:状态对象需持久化到共享存储,迁移需考虑分布式锁。
    • 中介者模式:中介者需支持集群部署,同事类注册需服务发现。
    • 备忘录模式:快照需存于共享存储,并处理并发更新冲突。
    • 模板方法模式:骨架流程涉及分布式事务时需引入 Saga 或 TCC。
    • 访问者/迭代器/解释器:通常运行在单节点,分布式需将数据分片后并行处理。

九、行为型模式与创建型、结构型模式的交叉应用

9.1 策略模式 + 工厂模式(策略工厂)

// 策略接口
interface DiscountStrategy { BigDecimal calc(BigDecimal price); }

// 策略工厂
@Component
class DiscountStrategyFactory {
    private Map<String, DiscountStrategy> strategies;
    
    @Autowired
    public DiscountStrategyFactory(List<DiscountStrategy> strategyList) {
        strategies = strategyList.stream()
            .collect(Collectors.toMap(s -> s.getClass().getSimpleName(), s -> s));
    }
    
    public DiscountStrategy get(String type) { return strategies.get(type); }
}

9.2 命令模式 + 建造者模式(复杂命令对象构建)

class EmailCommandBuilder {
    private String to, subject, body;
    private List<File> attachments = new ArrayList<>();
    
    public EmailCommandBuilder to(String to) { this.to = to; return this; }
    public EmailCommandBuilder withAttachment(File f) { attachments.add(f); return this; }
    public EmailCommand build() { return new EmailCommand(to, subject, body, attachments); }
}

9.3 观察者模式 + 单例模式(全局事件总线)

public enum GlobalEventBus {
    INSTANCE;
    private Set<EventListener> listeners = ConcurrentHashMap.newKeySet();
    public void register(EventListener l) { listeners.add(l); }
    public void post(Event e) { listeners.forEach(l -> l.onEvent(e)); }
}

9.4 访问者模式 + 组合模式(树形结构遍历)

interface Node { void accept(Visitor v); }
class CompositeNode implements Node {
    private List<Node> children = new ArrayList<>();
    public void accept(Visitor v) {
        v.visit(this);
        children.forEach(c -> c.accept(v));
    }
}

9.5 责任链模式 + 装饰器模式(链式包装)

class LoggingHandler extends HandlerDecorator {
    public LoggingHandler(Handler delegate) { super(delegate); }
    public void handle(Request req) {
        log.info("before");
        super.handle(req);
        log.info("after");
    }
}

9.6 模板方法模式 + 工厂方法模式(创建步骤延迟到子类)

abstract class DocumentProcessor {
    public final void process() {
        Document doc = createDocument(); // 工厂方法
        parse(doc);
        save(doc);
    }
    protected abstract Document createDocument();
}