【设计模式】命令模式 (动作(Action)模式或事务(Transaction)模式)宏命令

命令模式(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++)
  1. 为了用户使用方便,某系统提供了一系列功能键,用户可以自定义功能键的功能,例如功能键FunctionButton可以用于退出系统(由SystemExitClass类来实现),也可以用于显示帮助文档(由DisplayHelpClass类来实现)。
    用户可以通过修改配置文件来改变功能键的用途,现使用命令模式来设计该系统,使得功能键类与功能类之间解耦,可为同一个功能键设置不同的功能。
  1. 电视机遥控器
    电视机是请求的接收者,遥控器是请求的发送者,遥控器上有一些按钮,不同的按钮对应电视机的不同操作。抽象命令角色由一个命令接口来扮演,有三个具体的命令类实现了抽象命令接口,这三个具体命令类分别代表三种操作:打开电视机、关闭电视机和切换频道。显然,电视机遥控器就是一个典型的命令模式应用实例。

六、优缺点分析

优点 描述
解耦调用者与接收者 调用者无需了解具体执行细节,只需调用命令即可
支持撤销/重做、宏命令、日志记录等高级功能 通过命令对象可以轻松实现这些功能,可以比较容易地设计一个命令队列或宏命令(组合命令)为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案
扩展性好 新增命令只需添加新的 ConcreteCommand 类,符合开闭原则
缺点 描述
增加类的数量 每个命令都需要一个类,可能造成类爆炸
理解成本高 对于新手来说,结构略复杂,需要一定学习成本
性能开销 如果频繁创建命令对象,可能带来额外内存消耗

七、与其他模式对比(补充)

模式名称 目标
策略模式 封装算法,让算法可替换,强调"行为切换"
观察者模式 当对象状态改变时通知所有监听者
命令模式 将请求封装成对象,便于传递、存储、撤销等操作

八、最终小结

命令模式是一种非常灵活且强大的行为型设计模式,它适用于:

  • 需要解耦请求发送者和接收者的场景;
  • 需要支持撤销、重做、日志记录等高级功能;
  • 需要构建任务队列、宏命令、批处理机制等系统功能。

📌 一句话总结:

命令模式就像"遥控器",你按下按钮,背后谁在干活你不知道,但你知道事情能办成。


推荐使用方式:

  • 在需要撤销/重做的系统中优先考虑命令模式;
  • 结合"命令历史"实现多级撤销;
  • 使用"宏命令"批量执行多个命令;
  • 用 JSON 序列化保存命令状态,实现持久化。

九、扩展

实现命令队列

动机

  1. 当一个请求发送者发送一个请求时,有不止一个请求接收者产生响应,这些请求接收者将逐个执行业务方法,完成对请求的处理。
  2. 增加一个CommandQueue类,由该类负责存储多个命令对象,而不同的命令对象可以对应不同的请求接收者。
  3. 批处理。

实现

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)的形式永久存储在计算机中

  1. 为系统提供一种恢复机制
  2. 可以用于实现批处理
  3. 防止因为断电或者系统重启等原因造成请求丢失,而且可以避免重新发送全部请求时造成某些命令的重复执行

实现

将发送请求的命令对象通过序列化写到日志文件中

命令类必须使用属性[Serializable]标记为可序列化

实现撤销操作

实例

可以通过对命令类进行修改使得系统支持撤销(Undo)操作和恢复(Redo)操作。

设计一个简易计算器,该计算器可以实现简单的数学运算,还可以对运算实施撤销操作。

宏命令

动机

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

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