想象一下你开了一家智能餐厅。
如果不使用命令模式 ,客人的需求(请求)和厨师(执行)是强耦合的;而使用了命令模式,就相当于引入了"订单"这个核心对象。
1. 场景对比:口头传达 vs. 纸质订单
-
不使用模式(直接调用): 客人走进厨房直接冲厨师喊:"给我做个宫保鸡丁!"厨师必须立刻停下手头的活听你说话。如果你想取消,你得再冲进厨房喊停。如果厨师记性不好,或者人多了,厨房就乱套了。
-
使用命令模式(订单解耦): 客人把需求写在**订单(Command)**上交给服务员。服务员把订单插在排队架上。厨师根据订单一个个做。你可以轻松撤回订单,也可以记录今天一共卖了多少份。
2. 不使用模式:直接调用(硬编码版)
场景: 客人(Main)直接告诉服务员(Waiter)要做什么,服务员直接指挥厨师(Chef)。
缺点: 服务员类里全是 if-else。如果想加个"洗碗"或者"上菜"的功能,得改动服务员的代码;且无法记录和撤销订单。
#include <iostream>
#include <string>
// 接收者:厨师
class Chef {
public:
void cookPasta() { std::cout << "厨师:做了一份意大利面。\n"; }
void cookSteak() { std::cout << "厨师:做了一份西冷牛排。\n"; }
};
// 调用者:服务员(直接耦合)
class SimpleWaiter {
private:
Chef* chef;
public:
SimpleWaiter(Chef* c) : chef(c) {}
// 每增加一个菜品,这里就要改代码,增加 if-else
void order(std::string dish) {
std::cout << "服务员:收到 " << dish << " 的口头请求。\n";
if (dish == "Pasta") {
chef->cookPasta();
} else if (dish == "Steak") {
chef->cookSteak();
} else {
std::cout << "服务员:抱歉,没这道菜。\n";
}
}
};
int main() {
Chef* chef = new Chef();
SimpleWaiter* waiter = new SimpleWaiter(chef);
waiter->order("Pasta");
waiter->order("Steak");
delete waiter; delete chef;
return 0;
}
3. 使用命令模式:订单系统(解耦版)
场景: 引入了"订单"类。服务员不再直接喊厨师,而是管理订单队列。
优点: 增加新功能(如撤销、排队)不需要修改服务员类。
#include <iostream>
#include <vector>
#include <vector>
// 1. 接收者:厨师(具体的执行逻辑)
class Chef {
public:
void cookPasta() { std::cout << "厨师:正在烹饪意大利面...\n"; }
void cookSteak() { std::cout << "厨师:正在煎西冷牛排...\n"; }
};
// 2. 抽象命令类:订单基类
class Command {
protected:
Chef* chef;
public:
Command(Chef* c) : chef(c) {}
virtual ~Command() {}
virtual void execute() = 0; // 统一的执行接口
};
// 3. 具体命令:具体的订单
class PastaOrder : public Command {
public:
using Command::Command;
void execute() override { chef->cookPasta(); }
};
class SteakOrder : public Command {
public:
using Command::Command;
void execute() override { chef->cookSteak(); }
};
// 4. 调用者:高级服务员(只负责收集和触发订单)
class AdvancedWaiter {
private:
std::vector<Command*> orders; // 订单池(可以实现排队)
public:
void addOrder(Command* cmd) {
std::cout << "服务员:订单已记录。\n";
orders.push_back(cmd);
}
void cancelLastOrder() {
if (!orders.empty()) {
orders.pop_back();
std::cout << "服务员:已撤销最后一个订单。\n";
}
}
void notifyChef() {
std::cout << "\n--- 厨房开始出菜 ---\n";
for (auto cmd : orders) {
cmd->execute();
}
orders.clear();
}
};
// 5. 客户端测试
int main() {
Chef* chef = new Chef();
AdvancedWaiter* waiter = new AdvancedWaiter();
// 客户点菜(创建命令对象)
Command* pasta = new PastaOrder(chef);
Command* steak = new SteakOrder(chef);
waiter->addOrder(pasta);
waiter->addOrder(steak);
// 撤销演示
waiter->cancelLastOrder(); // 牛排被撤销了
// 统一执行
waiter->notifyChef();
delete pasta; delete steak; delete waiter; delete chef;
return 0;
}
4. 优缺点深度对比
不使用模式的优缺点
-
优点: * 简单直接: 几行代码搞定,没有复杂的类继承。
- 性能: 没有额外的对象创建开销。
-
缺点:
-
硬编码: 每次想加新菜,都得去改
SimpleWaiter的if-else代码。 -
无法撤销: 请求发出去就执行了,很难做排队、记录或撤销功能。
-
使用命令模式的优缺点
-
优点:
-
解耦(Decoupling): 发送请求的人(客人)完全不需要知道执行请求的人(厨师)具体是怎么工作的。
-
扩展性(Scalability): 想增加新菜?写个新类继承
Command就行,完全不用改现有的代码(符合开闭原则)。 -
功能强大: 容易实现撤销(Undo) 、重做(Redo) 、延时执行 和请求日志记录。
-
-
缺点:
-
类爆炸: 每个小操作都要写一个具体的命令类,代码量激增。
-
复杂度: 增加了系统的理解难度。
-
总结对比
| 特性 | 不用模式(直接调用) | 使用命令模式 |
|---|---|---|
| 耦合度 | 极高。服务员必须知道厨师的所有方法。 | 低。服务员只认识"订单"对象。 |
| 灵活性 | 很难实现撤销、重做、任务排队。 | 轻松实现订单队列、日志记录和 Undo/Redo。 |
| 扩展性 | 每加一个功能都要修改 SimpleWaiter。 |
只需写新的 Command 子类,不影响现有逻辑。 |
| 代码量 | 代码少,逻辑散。 | 代码多,结构清晰,符合"开闭原则"。 |
5. 什么时候该用它?(进阶判断)
你可以问自己三个问题,如果答案为"是",就用它:
-
我是否需要支持"撤销/重做"功能?
-
我是否需要把请求排队、记录日志或者延迟执行?
-
我的系统是否需要支持"插件式"的扩展(即增加新功能不修改老代码)?