Skip to content

行为型补充 — 七种行为型设计模式 ​

一句话记忆口诀:命令封装请求、迭代器遍历集合、中介者解耦交互、备忘录保存快照、状态组织行为、访问者双分派、解释器解析语法。

1. 命令模式(Command Pattern) ​

1.1 引入:它解决了什么问题? ​

当需要将请求参数化、排队执行、记录历史或支持撤销时,请求的选择、执行与历史管理容易耦合在调用点,难以统一扩展:

java
// ❌ 反例:遥控器直接调用设备方法,请求与具体操作耦合,撤销、排队、记录历史都需额外封装
public class RemoteControl {
    public void pressButton(String device, String action) {
        if ("light".equals(device) && "on".equals(action)) {
            light.turnOn();
        } else if ("light".equals(device) && "off".equals(action)) {
            light.turnOff();
        } else if ("tv".equals(device) && "on".equals(action)) {
            tv.powerOn();
        }
        // 请求分散在 if-else 中,难以统一撤销、排队或记录历史
    }
}

问题根因:请求缺少独立的表示,调用者直接编排具体操作,导致请求的选择、执行与历史管理耦合在一起。命令模式把请求封装为对象,使调用者能在不依赖具体接收者的情况下提交操作,也为排队、记录、重试或撤销提供统一扩展点。

1.2 类比与定义 ​

生活类比:餐厅点餐。顾客(发送者)不直接对厨师(接收者)喊菜,而是写在订单(命令对象)上交给服务员。订单可以排队、可以取消、可以记录历史。

命令模式将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录日志,以及支持可撤销的操作。

1.3 原理与实现 ​

java
// ===== 命令接口 =====
public interface Command {
    void execute();
    void undo(); // 支持撤销
}

// ===== 具体命令 =====
public class LightOnCommand implements Command {
    private Light light;
    public LightOnCommand(Light light) { this.light = light; }
    @Override
    public void execute() { light.turnOn(); }
    @Override
    public void undo() { light.turnOff(); } // 撤销 = 关灯
}

// ===== 调用者:遥控器 =====
public class RemoteControl {
    private final Deque<Command> history = new ArrayDeque<>();

    public void pressButton(Command command) {
        command.execute();
        history.push(command);
    }

    public void pressUndo() {
        if (!history.isEmpty()) {
            history.pop().undo();
        }
    }
}

// ===== 使用 =====
Light light = new Light();
RemoteControl remote = new RemoteControl();
remote.pressButton(new LightOnCommand(light)); // 开灯
remote.pressUndo(); // 撤销 → 关灯

1.4 在 Spring / JDK 中的应用 ​

框架/类说明
Runnable / Callable将任务封装为对象,提交给线程池执行
线程池 ThreadPoolExecutor命令队列(BlockingQueue)存储待执行的任务
Spring Batch Step将一段批处理工作组织为可执行步骤,具有任务封装特征
javax.swing.ActionSwing 中的动作命令

1.5 关键边界与常见误解 ​

命令模式只解决「把请求封装成对象」,它本身并不附带以下能力,需要在具体场景中另行设计:

  • 幂等与重试:命令对象不保证幂等,重试可能重复产生副作用,是否安全重试取决于具体命令的实现。
  • 撤销与补偿:undo() 不是普遍存在的逆操作。对涉及外部系统调用的命令,撤销通常需要显式的补偿逻辑,而非简单的逆向方法。上面的历史栈只记录 execute() 正常返回的命令,不保证操作的原子性,也不涵盖部分失败后的补偿。
  • 排队与持久化:BlockingQueue 只提供内存中的任务排队,不包含持久化、进程崩溃恢复或可靠投递语义。
  • 框架映射:Runnable / Callable 是命令式的任务抽象,ThreadPoolExecutor 是执行与调度设施,Spring Batch 的 Step 是带命令式特征的批处理步骤抽象,都不宜不加限定地等同于 GoF 命令模式本身。

这些能力是否必需、如何实现,取决于具体场景,而不是命令模式的必备特征。

2. 迭代器、中介者、备忘录、状态、访问者:进入单页详解 ​

这五个模式的完整讲解各自单独成页,包含生活类比、烂代码对比、完整实现、框架应用和适用场景,本页不重复展开:

  • 迭代器模式——把遍历从集合中抽离,客户端不再关心底层结构。
  • 中介者模式——把对象间的网状调用收拢到中介者,降为星型拓扑。
  • 备忘录模式——在不破坏封装的前提下捕获状态快照,用于撤销与回滚。
  • 状态模式——把每个状态封装成类,用状态机替代 if-else,状态转换集中到状态对象。
  • 访问者模式——在数据结构稳定的前提下,把多变的操作集中到访问者里。

3. 解释器模式(Interpreter Pattern) ​

3.1 引入:它解决了什么问题? ​

当需要解释执行一种特定的"语言"或"表达式"时,硬编码解析逻辑会导致代码难以扩展。

3.2 类比与定义 ​

生活类比:翻译官。不同语言(表达式)有不同的语法规则,翻译官(解释器)按照语法规则逐步解析并翻译。

解释器模式给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

3.3 原理与实现 ​

java
// ===== 抽象表达式 =====
public interface Expression {
    int interpret();
}

// ===== 终结符表达式:数字 =====
public class NumberExpression implements Expression {
    private int number;
    public NumberExpression(int number) { this.number = number; }
    @Override
    public int interpret() { return number; }
}

// ===== 非终结符表达式:加法 =====
public class AddExpression implements Expression {
    private Expression left, right;
    public AddExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }
    @Override
    public int interpret() {
        return left.interpret() + right.interpret();
    }
}

// ===== 非终结符表达式:乘法 =====
public class MultiplyExpression implements Expression {
    private Expression left, right;
    public MultiplyExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }
    @Override
    public int interpret() {
        return left.interpret() * right.interpret();
    }
}

// ===== 使用:构建表达式树并解释执行 =====
// 表达式:(3 + 5) * 2
Expression expr = new MultiplyExpression(
    new AddExpression(new NumberExpression(3), new NumberExpression(5)),
    new NumberExpression(2)
);
System.out.println(expr.interpret()); // 输出 16

3.4 在 Spring / JDK 中的应用 ​

框架/类说明
Spring EL (SpEL)#{user.name} 表达式解析
正则表达式 Pattern正则语法的解释执行
MyBatis 动态 SQL<if>、<where> 等标签的解析
java.text.Format日期/数字格式化表达式

以上多为涉及表达式或语法解析的相关机制,并不都严格实现经典 GoF 解释器模式:Pattern 更接近正则的编译与匹配机制,java.text.Format 更接近格式化而非语法树解析。

3.5 适用条件与局限 ​

  • 适用:语法简单、效率要求不高的场景(如配置解析、规则引擎)
  • 不适用:复杂语法(应使用专业的解析器生成工具如 ANTLR)

4. 七种模式对比总结 ​

模式核心思想解决的问题关键词
命令模式封装请求请求需要排队/撤销/记录请求对象化、Undo
迭代器模式统一遍历不同集合遍历方式不一致hasNext/next
中介者模式集中交互对象间网状耦合星型拓扑
备忘录模式保存快照需要撤销/回滚到历史状态状态快照、Undo Log
状态模式状态驱动行为随状态变化的 if-else状态机、分支收敛
访问者模式分离操作数据结构稳定但操作多变双分派
解释器模式解析语法需要解释执行特定语言表达式树、文法

复习检验标准:能否口述"这个模式解决了什么问题?不用它会怎样?Spring 中哪里用到了?"

Released under the MIT License.