跳转至

命令模式

分类:行为型模式。来源:《Head First 设计模式》第 6 章。

类结构

classDiagram
    direction TB
    class Client
    class Invoker {
        +setCommand(Command command)
        +invoke()
    }
    class Command {
        <<interface>>
        +execute()
    }
    class ConcreteCommand {
        -Receiver receiver
        +execute()
    }
    class Receiver {
        +action()
    }
    Client ..> ConcreteCommand : 创建并装配
    Client ..> Receiver : 配置接收者
    Invoker --> Command : 触发执行
    Command <|.. ConcreteCommand
    ConcreteCommand --> Receiver : 调用业务操作

角色定义

角色 定义
Client(客户) 创建或选择接收者,构造具体命令,并把命令交给调用者。
Invoker(调用者) 保存命令引用,在合适时机调用 execute(),无需了解具体业务。
Command(命令接口) 声明统一的执行入口;需要撤销时可以进一步扩展相应接口。
ConcreteCommand(具体命令) 封装接收者及执行请求所需的数据,将执行操作委托给接收者。
Receiver(接收者) 提供真正完成业务工作的操作。

定义命令模式

命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。

参与对象

  • 命令对象封装要执行的请求,并持有实际执行操作的接收者。
  • 调用者通过统一的命令接口发起操作,不必知道接收者的具体实现。
  • 接收者执行实际业务逻辑;具体命令决定调用哪些操作。

例如,遥控器按钮只调用命令的 execute(),开灯命令再调用电灯对象的 on()。需要撤销时,由命令保存必要状态并实现对应的撤销操作。

原页中的其他模式摘记已分别整理为适配器模式和模板方法模式。

使用钩子

参见模板方法:使用钩子。

好莱坞原则

参见模板方法:好莱坞原则。

用模版方法排序

参见模板方法:用模板方法排序。

Head First 示例:支持撤销的遥控器

第 6 章用遥控器说明如何把“按按钮”与“设备怎样工作”分开。这里选用作者 2020 年第二版配套源码的 command/undo 版本,只画灯光控制,省略吊扇等其他设备。

这个版本的灯光命令会先保存执行前的亮度,撤销时调用 dim(level) 恢复;不是简单地把“开”改成“关”。遥控器只记录最近一次命令,图中不表示多级撤销历史。

示例类型 模式角色 职责
RemoteControlWithUndo 调用者 保存开、关命令数组及最近执行的命令,响应按钮操作。
Command 命令接口 声明 execute() 与 undo() 两个统一入口。
LightOnCommand / LightOffCommand 具体命令 保存灯和原亮度,执行开灯、关灯并支持恢复亮度。
NoCommand 空命令 为未配置的插槽提供不执行任何工作的命令对象。
Light 接收者 实际提供开、关、调光和读取当前亮度的操作。

示例 UML

classDiagram
    direction TB
    class RemoteControlWithUndo {
        Command[] onCommands
        Command[] offCommands
        Command undoCommand
        +setCommand(slot, onCommand, offCommand)
        +onButtonWasPushed(slot)
        +offButtonWasPushed(slot)
        +undoButtonWasPushed()
    }
    class Command {
        <<interface>>
        +execute()
        +undo()
    }
    class LightOnCommand {
        int level
        +execute()
        +undo()
    }
    class LightOffCommand {
        int level
        +execute()
        +undo()
    }
    class NoCommand {
        +execute()
        +undo()
    }
    class Light {
        +on()
        +off()
        +dim(int level)
        +getLevel() int
    }
    RemoteControlWithUndo --> Command : 插槽与最近命令
    Command <|.. LightOnCommand
    Command <|.. LightOffCommand
    Command <|.. NoCommand
    LightOnCommand --> Light : 控制并恢复亮度
    LightOffCommand --> Light : 控制并恢复亮度

协作过程

  1. RemoteLoader 创建灯和开、关命令,再用 setCommand() 装入遥控器插槽。
  2. 按下开灯按钮,遥控器调用开灯命令的 execute(),命令先读原亮度,再调用 Light.on()。
  3. 遥控器把刚执行的命令赋给 undoCommand。
  4. 按撤销按钮时调用该命令的 undo(),命令用保存的亮度恢复灯的状态。

配套源码(2020 年第二版,固定版本):

评论