关注

命令模式详解

命令模式(Command Pattern)详解


一、核心定义 & 本质

官方定义
将一个请求封装为一个对象,从而使你可以:

  • 用不同请求对客户进行参数化
  • 对请求进行排队或记录日志
  • 支持可撤销/重做的操作

** 通俗大白话**
「做什么」「谁去做」 彻底分离。
命令对象就像 遥控器按钮:既知道要执行什么动作,又自带“如何撤销”的说明书。
调用者只负责 按下按钮,不关心 灯泡怎么亮,实现极致解耦。


二、五大角色 & 结构示意图

1. 角色映射表

角色英文职责生活类比
命令接口Command声明 execute() / undo()遥控器统一接口
具体命令ConcreteCommand绑定接收者,实现执行/撤销逻辑“开灯按钮”、“换台按钮”
调用者Invoker持有命令对象,触发 execute()遥控器外壳/主板
接收者Receiver真正执行具体业务逻辑灯泡、电视、数据库
客户端Client组装命令、绑定接收者、交给调用者用户/配置脚本

2. 类结构图 (Mermaid)

创建并绑定

注入命令

依赖抽象

实现

关联执行者

«interface»

Command

+execute()

+undo()

ConcreteCommand

-Receiver receiver

+execute()

+undo()

Invoker

-Command command

+setCommand(Command)

+pressButton()

+pressUndo()

Receiver

+actionA()

+actionB()

Client

+main()


三、工作原理 (执行时序)

Receiver(接收者) Invoker(调用者) ConcreteCommand(具体命令) Client(客户端) Receiver(接收者) Invoker(调用者) ConcreteCommand(具体命令) Client(客户端) opt [撤销操作] new ConcreteCommand(receiver) invoker.setCommand(cmd) execute() receiver.action() 完成 返回结果 undo() receiver.reverseAction()

四、优缺点 & 适用场景

1. 优缺点对照

维度说明
优点① 调用者与接收者彻底解耦
② 新增命令只需实现接口,符合开闭原则
③ 天然支持撤销/重做/排队/日志/事务
④ 支持组合命令(宏命令)一键执行多步操作
缺点① 每个命令一个类,可能导致 类爆炸
② 引入额外抽象层,初期调试成本略高
③ 过度使用会使简单逻辑变复杂

2. 适用场景

  1. GUI 按钮/菜单/快捷键与业务逻辑分离
  2. 需要撤销/重做:文本编辑器、绘图软件、IDE
  3. 任务队列/日志记录:消息队列、操作审计
  4. 事务处理:数据库事务、工作流引擎(全成功或全回滚)
  5. 宏命令/脚本化:一键执行“保存+编译+运行”

五、代码实现 (Java & C++)

Java 实现:智能家居遥控器

// 1. 命令接口
public interface Command {
    void execute();
    void undo();
}

// 2. 接收者:灯光
public class Light {
    private final String location;
    public Light(String location) { this.location = location; }
    public void on()  { System.out.println(location + " 灯光已打开"); }
    public void off() { System.out.println(location + " 灯光已关闭"); }
}

// 3. 具体命令:开灯
public class LightOnCommand implements Command {
    private final Light light;
    public LightOnCommand(Light light) { this.light = light; }
    @Override public void execute() { light.on(); }
    @Override public void undo()    { light.off(); }
}

// 4. 调用者:遥控器
public class RemoteControl {
    private Command command;
    public void setCommand(Command command) { this.command = command; }
    public void pressButton() { if(command != null) command.execute(); }
    public void pressUndo()   { if(command != null) command.undo(); }
}

// 5. 客户端测试
public class Client {
    public static void main(String[] args) {
        Light livingRoom = new Light("客厅");
        Command on  = new LightOnCommand(livingRoom);
        Command off = new LightOffCommand(livingRoom); // 假设已定义
        
        RemoteControl remote = new RemoteControl();
        remote.setCommand(on); remote.pressButton(); // 开
        remote.pressUndo();                          // 关(撤销)
    }
}

C++ 现代实现(智能指针+多态)

#include <iostream>
#include <memory>
using namespace std;

class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};

class TV {
public:
    void on() { cout << "电视已打开\n"; }
    void off() { cout << "电视已关闭\n"; }
    void setChannel(int ch) { channel = ch; cout << "切换至频道 " << ch << "\n"; }
    int getChannel() const { return channel; }
private:
    int channel = 0;
};

class TVOnCommand : public Command {
public:
    explicit TVOnCommand(TV* tv) : tv(tv) {}
    void execute() override { tv->on(); }
    void undo() override    { tv->off(); }
private:
    TV* tv;
};

class TVChangeChannelCommand : public Command {
public:
    TVChangeChannelCommand(TV* tv, int newCh) : tv(tv), newCh(newCh), oldCh(0) {}
    void execute() override { oldCh = tv->getChannel(); tv->setChannel(newCh); }
    void undo() override    { tv->setChannel(oldCh); }
private:
    TV* tv;
    int newCh, oldCh;
};

class RemoteControl {
public:
    void setCommand(unique_ptr<Command> cmd) { this->cmd = move(cmd); }
    void press()  { if(cmd) cmd->execute(); }
    void undo()   { if(cmd) cmd->undo(); }
private:
    unique_ptr<Command> cmd;
};

int main() {
    TV tv;
    RemoteControl remote;
    
    remote.setCommand(make_unique<TVOnCommand>(&tv));
    remote.press(); remote.undo();
    
    remote.setCommand(make_unique<TVChangeChannelCommand>(&tv, 5));
    remote.press(); remote.undo();
    return 0;
}

六、真实场景深度拆解(面试高频)

场景1:文本编辑器(撤销/重做)

核心逻辑:每一步操作 = 独立命令对象。编辑器维护两个栈。

封装

弹出

弹出

用户操作

Command对象

execute执行

压入历史栈

Ctrl+Z 撤销

调用 undo

压入重做栈

Ctrl+Y 重做

再次 execute

压回历史栈

** 不用命令模式的后果**:编辑器源码写满 if(type==DELETE) / if(type==INPUT),新增操作必改核心类,严重违反开闭原则。

** 一句话背诵**
编辑器把每一步操作打包成命令,用栈存起来。Ctrl+Z 调 undo,Ctrl+Y 重调 execute。


场景2:数据库事务(原子性保证)

核心逻辑:每条 SQL = 命令对象。事务管理器维护命令队列,支持 commit / rollback

无异常

异常

开启事务

SQL命令A入队

SQL命令B入队

SQL命令C入队

Commit: 顺序遍历 execute

成功: 清空队列

Rollback: 倒序遍历 undo

数据回滚至初始状态

** 为什么是命令模式?**

  • 上层业务只关心 commit/rollback,不写死每条 SQL 怎么回滚
  • 事务管理器 = Invoker,SQL 命令 = ConcreteCommand
  • 批量执行 + 批量撤销 = 命令队列天然能力

** 一句话背诵**
事务把SQL打包成命令队列。commit 顺序执行,rollback 倒序撤销,保证原子性。


七、与其他模式对比 & 面试避坑

1. 易混淆模式对比

模式核心目的关键区别
命令模式解耦请求发起者与执行者,支持撤销/排队请求是对象,可存储、传递、回滚
策略模式封装算法族,运行时切换执行逻辑侧重算法替换,无撤销/排队概念
备忘录模式保存对象内部状态,用于恢复侧重状态快照,不封装行为逻辑

2. 面试高频陷阱

  • “命令模式就是写个接口让调用者调用”
    → 必须包含 executeundo/rollback,且能脱离接收者独立存在/序列化。
  • “撤销就是备忘录模式”
    → 命令的 undo反向执行逻辑(如 insert 对应 delete),备忘录是直接恢复旧状态
  • 宏命令/组合命令:通过 Composite 模式将多个命令组合成一个大命令,一键执行(如“保存全部+关闭文档”)。

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2503_90237760/article/details/161185318

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--