命令模式有哪些角色?调用流程是什么?
简化版
命令模式通常有四个角色:Command 定义命令接口,ConcreteCommand 封装具体请求,Receiver 执行业务动作,Invoker 触发命令。调用流程是客户端创建命令并绑定接收者,Invoker 持有命令,用户触发后 Invoker 调用 execute()。
详细版
命令模式的典型角色包括:
| 角色 | 作用 |
|---|---|
| Command | 抽象命令接口,通常定义 execute() |
| ConcreteCommand | 具体命令,持有 Receiver,并调用 Receiver 的业务方法 |
| Receiver | 真正完成业务动作的对象 |
| Invoker | 请求触发者,只负责调用命令 |
| Client | 负责组装命令、接收者和调用者 |
典型调用流程:
Client 创建 Receiver
Client 创建 ConcreteCommand,并传入 Receiver
Client 把 Command 设置给 Invoker
Invoker 在合适时机调用 command.execute()
ConcreteCommand 调用 Receiver 完成真实业务
命令模式的重点是让 Invoker 和 Receiver 解耦。Invoker 不知道业务怎么做,只知道执行命令。
完整版教学
一、Command:统一命令入口
命令接口通常很简单:
interface Command {
void execute();
}
如果要支持撤销,可以扩展为:
interface Command {
void execute();
void undo();
}
Command 的作用是给调用方一个统一入口。无论背后命令多复杂,Invoker 都只需要调用 execute()。
二、ConcreteCommand:把请求和执行者绑定起来
具体命令负责封装一次请求。
class LightOnCommand implements Command {
private final Light light;
LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.on();
}
}
它通常会持有 Receiver,然后在 execute() 里调用 Receiver 的方法。
注意,ConcreteCommand 不是业务实体本身,它更像“操作单”或“任务单”。
三、Receiver:真正干活的人
Receiver 才是真正执行业务逻辑的对象。
class Light {
void on() {
System.out.println("开灯");
}
void off() {
System.out.println("关灯");
}
}
命令对象不应该承载所有复杂业务。真实业务逻辑仍然应该放在领域对象或服务对象里,命令负责调度和封装请求。
四、Invoker:只负责触发命令
Invoker 是请求触发者,例如按钮、菜单项、遥控器、任务调度器。
class RemoteButton {
private Command command;
void setCommand(Command command) {
this.command = command;
}
void press() {
command.execute();
}
}
Invoker 不需要知道 Light,也不需要知道 on() 方法。它只知道执行命令。
五、Client:负责装配关系
Client 负责把对象组装起来:
Light light = new Light();
Command command = new LightOnCommand(light);
RemoteButton button = new RemoteButton();
button.setCommand(command);
button.press();
在 Spring 项目里,这种装配可能由容器完成,也可能通过工厂或配置表完成。
六、调用流程背后的解耦意义
命令模式最重要的解耦是:
Invoker 不依赖 Receiver
Invoker 只依赖 Command 抽象
ConcreteCommand 负责连接 Command 和 Receiver
这样 Invoker 可以绑定不同命令:
- 开灯命令;
- 关灯命令;
- 播放音乐命令;
- 执行批处理命令。
Invoker 的代码完全不用改。
七、常见误区与追问
角色划分的验收可以沿一条调用链检查依赖方向。客户端装配1个 Invoker、1个具体命令和1个 Receiver 后,运行期应由 Invoker 只调用 execute(),再由命令转给 Receiver。若 Invoker 直接识别 Receiver 类型,或者 Receiver 反向依赖界面按钮,角色边界已经被破坏。
| 检查维度 | 判定依据 |
|---|---|
| Invoker | 保存并触发抽象命令 |
| ConcreteCommand | 绑定参数与 Receiver |
记忆钩子:Client 负责“组装”,Invoker 负责“按下”,Receiver 负责“执行”。
- 误区:Client 是最终用户。 模式角色中的 Client 是创建并装配对象关系的代码,不一定是 UI 用户。
- 追问:Invoker 能否保存多个命令? 可以,菜单、快捷键和调度器都常维护命令集合。
- 误区:Receiver 必须只有一个。 一个宏命令可以协调多个 Receiver,但要避免把全部业务塞进命令。
- 追问:ConcreteCommand 为什么需要 Receiver? 它把抽象请求翻译为具体业务调用,从而隔离触发者与执行者。
- 追问:怎样测试角色解耦? 用假命令验证 Invoker 只触发接口,再单测具体命令是否正确调用 Receiver。
八、加强记忆
命令模式的角色可以记成“调用者按按钮,命令对象接单,接收者干活”。Invoker 只触发命令,ConcreteCommand 封装请求并调用 Receiver,Receiver 负责真实业务,Client 负责把它们装配起来。