命令模式(Command Pattern)详解
一、命令模式简介
命令模式(Command Pattern) 是一种 行为型设计模式(对象行为型模式),它将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,并支持请求的排队、记录日志、撤销等操作。
别名 为动作(Action)模式或事务(Transaction)模式
简单来说:
"把一次方法调用包装成一个对象,这样你就可以像处理普通对象一样来处理这些请求。"
将请求发送者和接收者完全解耦
发送者与接收者之间没有直接引用关系
发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求

相同的开关可以通过不同的电线来控制不同的电器
开关 <- -> 请求发送者
电灯<- -> 请求的最终接收者和处理者
开关和电灯之间并不存在直接耦合关系,它们通过电线连接在一起,使用不同的电线可以连接不同的请求接收者
命令模式包含以下4个角色 :
Command(抽象命令类)
ConcreteCommand(具体命令类)
Invoker(调用者)
Receiver(接收者)

命令模式的本质是对请求进行封装。
一个请求对应于一个命令,将发出命令的责任和执行命令的责任分开。
命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。
二、解决的问题类型
命令模式主要用于解决以下问题:
- 解耦请求发送者和接收者:发送者不需要知道具体执行逻辑,只需要知道如何发送"命令"。
- 支持撤销/重做功能:通过保存命令历史,可以轻松实现撤销或重做操作。
- 支持事务回滚、日志记录、队列任务等功能:命令可以被记录、排队、延迟执行。(在不同的时间指定请求、将请求排队和执行请求)
- 系统需要将一组操作组合在一起形成宏命令
三、使用场景
场景 | 示例 |
---|---|
支持撤销操作 | 文本编辑器中的"撤销"、"重做"按钮 |
解耦调用方与执行方 | 遥控器控制多个家电设备 |
实现宏命令 | 执行一组命令的组合操作 |
任务队列 | 将多个命令加入队列异步执行 |
日志与恢复 | 记录命令执行日志,系统崩溃后可恢复 |
四、核心角色
角色 | 描述 |
---|---|
Command(命令接口) | 定义执行命令的方法,如 execute() |
ConcreteCommand(具体命令) | 实现 Command 接口,绑定具体的接收者(Receiver)并调用其方法 |
Invoker(调用者) | 持有命令对象,负责调用命令的 execute() 方法 |
Receiver(接收者) | 实际执行命令的对象,包含业务逻辑 |
Client(客户端) | 创建命令对象并设置接收者,将命令交给调用者执行 |
五、代码案例(Java)
我们以"智能遥控器控制家电"为例来演示命令模式的使用。
1. 定义命令接口
java
// 命令接口
interface Command {
void execute();
void undo(); // 可选:用于撤销操作
}
2. 创建接收者类(实际执行动作的对象)
java
// 灯的接收者
class Light {
public void on() {
System.out.println("The light is ON");
}
public void off() {
System.out.println("The light is OFF");
}
}
// 风扇的接收者
class Fan {
public void start() {
System.out.println("The fan is STARTING");
}
public void stop() {
System.out.println("The fan is STOPPING");
}
}
3. 创建具体命令类
java
// 开灯命令
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();
}
}
// 关风扇命令
class FanOffCommand implements Command {
private Fan fan;
public FanOffCommand(Fan fan) {
this.fan = fan;
}
@Override
public void execute() {
fan.stop();
}
@Override
public void undo() {
fan.start();
}
}
4. 创建调用者(比如遥控器)
java
// 遥控器
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
public void pressUndo() {
command.undo();
}
}
5. 客户端测试代码
java
public class Client {
public static void main(String[] args) {
// 创建接收者
Light light = new Light();
Fan fan = new Fan();
// 创建具体命令
Command lightOn = new LightOnCommand(light);
Command fanOff = new FanOffCommand(fan);
// 创建调用者
RemoteControl remote = new RemoteControl();
// 设置命令并执行
remote.setCommand(lightOn);
remote.pressButton(); // 输出:The light is ON
remote.pressUndo(); // 输出:The light is OFF
System.out.println("----------");
remote.setCommand(fanOff);
remote.pressButton(); // 输出:The fan is STOPPING
remote.pressUndo(); // 输出:The fan is STARTING
}
}
典型代码(C++)
典型的抽象命令类代码
cpp
abstract class Command
{
public abstract void Execute();
}
典型的调用者(请求发送者)类代码
cpp
class Invoker
{
private Command command;
//构造注入
public Invoker(Command command)
{
this.command=command;
}
public Command Command
{
get { return command; }
//设值注入
set { command = value; }
}
//业务方法,用于调用命令类的方法
public void Call()
{
command.Execute();
}
}
典型的具体命令类代码
cpp
class ConcreteCommand : Command
{
private Receiver receiver; //维持一个对请求接收者对象的引用
public override void Execute()
{
receiver.Action();//调用请求接收者的业务处理方法Action()
}
}
典型的请求接收者类代码
cpp
class Receiver
{
public void Action()
{
//具体操作
}
}
其他案例(C++)
- 为了用户使用方便,某系统提供了一系列功能键,用户可以自定义功能键的功能,例如功能键FunctionButton可以用于退出系统(由SystemExitClass类来实现),也可以用于显示帮助文档(由DisplayHelpClass类来实现)。
用户可以通过修改配置文件来改变功能键的用途,现使用命令模式来设计该系统,使得功能键类与功能类之间解耦,可为同一个功能键设置不同的功能。

- 电视机遥控器
电视机是请求的接收者,遥控器是请求的发送者,遥控器上有一些按钮,不同的按钮对应电视机的不同操作。抽象命令角色由一个命令接口来扮演,有三个具体的命令类实现了抽象命令接口,这三个具体命令类分别代表三种操作:打开电视机、关闭电视机和切换频道。显然,电视机遥控器就是一个典型的命令模式应用实例。
六、优缺点分析
优点 | 描述 |
---|---|
✅ 解耦调用者与接收者 | 调用者无需了解具体执行细节,只需调用命令即可 |
✅ 支持撤销/重做、宏命令、日志记录等高级功能 | 通过命令对象可以轻松实现这些功能,可以比较容易地设计一个命令队列或宏命令(组合命令)为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案 |
✅ 扩展性好 | 新增命令只需添加新的 ConcreteCommand 类,符合开闭原则 |
缺点 | 描述 |
---|---|
❌ 增加类的数量 | 每个命令都需要一个类,可能造成类爆炸 |
❌ 理解成本高 | 对于新手来说,结构略复杂,需要一定学习成本 |
❌ 性能开销 | 如果频繁创建命令对象,可能带来额外内存消耗 |
七、与其他模式对比(补充)
模式名称 | 目标 |
---|---|
策略模式 | 封装算法,让算法可替换,强调"行为切换" |
观察者模式 | 当对象状态改变时通知所有监听者 |
命令模式 | 将请求封装成对象,便于传递、存储、撤销等操作 |
八、最终小结
命令模式是一种非常灵活且强大的行为型设计模式,它适用于:
- 需要解耦请求发送者和接收者的场景;
- 需要支持撤销、重做、日志记录等高级功能;
- 需要构建任务队列、宏命令、批处理机制等系统功能。
📌 一句话总结:
命令模式就像"遥控器",你按下按钮,背后谁在干活你不知道,但你知道事情能办成。
✅ 推荐使用方式:
- 在需要撤销/重做的系统中优先考虑命令模式;
- 结合"命令历史"实现多级撤销;
- 使用"宏命令"批量执行多个命令;
- 用 JSON 序列化保存命令状态,实现持久化。
九、扩展
实现命令队列
动机:
- 当一个请求发送者发送一个请求时,有不止一个请求接收者产生响应,这些请求接收者将逐个执行业务方法,完成对请求的处理。
- 增加一个CommandQueue类,由该类负责存储多个命令对象,而不同的命令对象可以对应不同的请求接收者。
- 批处理。
实现:
cpp
using System.Collections.Generic;
namespace CommandSample
{
class CommandQueue
{
//定义一个List来存储命令队列
private List<Command> commands = new List<Command>();
public void AddCommand(Command command)
{
commands.Add(command);
}
public void RemoveCommand(Command command)
{
commands.Remove(command);
}
//循环调用每一个命令对象的Execute()方法
public void Execute()
{
foreach (object command in commands)
{
((Command)command).Execute();
}
}
}
}
记录请求日志
动机 :
将请求的历史记录保存下来,通常以日志文件(Log File)的形式永久存储在计算机中
- 为系统提供一种恢复机制
- 可以用于实现批处理
- 防止因为断电或者系统重启等原因造成请求丢失,而且可以避免重新发送全部请求时造成某些命令的重复执行
实现 :
将发送请求的命令对象通过序列化写到日志文件中
命令类必须使用属性[Serializable]标记为可序列化
实现撤销操作
实例 :
可以通过对命令类进行修改使得系统支持撤销(Undo)操作和恢复(Redo)操作。
设计一个简易计算器,该计算器可以实现简单的数学运算,还可以对运算实施撤销操作。

宏命令
动机:
- 宏命令(Macro Command)又称为组合命令(Composite Command),它是组合模式和命令模式联用的产物
- 宏命令是一个具体命令类,它拥有一个集合,在该集合中包含了对其他命令对象的引用
- 当调用宏命令的Execute()方法时,将递归调用它所包含的每个成员命令的Execute()方法。一个宏命令的成员可以是简单命令,还可以继续是宏命令
- 执行一个宏命令将触发多个具体命令的执行,从而实现对命令的批处理

部分内容由AI大模型生成,注意识别!