一、为什么用命令模式
在软件设计中,命令模式(Command Pattern)提供了一种将请求封装为对象的方式,从而使请求的发送者和执行者解耦。这在以下场景非常有用:当操作需要支持撤销、排队、延迟执行或日志记录时,命令模式能将逻辑统一管理。
餐厅点餐系统是典型场景:服务员记录顾客订单,厨师按顺序制作,每道菜可独立撤销或取消,但正在制作的菜不能撤销。直接在服务员或厨师中处理复杂逻辑,会导致代码耦合,难以扩展。使用命令模式,将"下单""做菜""撤销"操作封装成独立命令对象,既清晰又易维护。
二、场景说明
在这个场景中,餐厅的服务员负责接收顾客点餐。每道菜都是一个独立的命令对象,例如"红烧鱼"和"家乡煎豆腐"。顾客下单后,服务员将订单加入队列,但厨师是按顺序做菜的,当前只做一道菜,做完后才开始下一道。
系统支持顾客在下单后撤销某些菜,但有严格限制:正在烹饪的菜不可撤销,只允许撤销尚未开始的菜。服务员可以继续接收新订单,撤销旧订单,厨师按照队列顺序烹饪所有菜品。这样既满足实际业务需求,也充分体现了命令模式的灵活性和可扩展性。
三、类图

四、C++代码实现
cpp
#include <iostream>
#include <memory>
#include <map>
#include <deque>
#include <algorithm>
enum class OrderStatus { Pending, Cooking, Done };
// 命令接口
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() = 0;
};
// 厨师
class Chef {
public:
void cookFish() { std::cout << "厨师:开始做红烧鱼!\n"; }
void cancelFish() { std::cout << "厨师:取消红烧鱼。\n"; }
void cookTofu() { std::cout << "厨师:开始做家乡煎豆腐!\n"; }
void cancelTofu() { std::cout << "厨师:取消家乡煎豆腐。\n"; }
};
// 具体命令
class FishCommand : public Command {
public:
FishCommand(Chef* chef) : chef_(chef) {}
void execute() override { chef_->cookFish(); }
void undo() override { chef_->cancelFish(); }
private:
Chef* chef_;
};
class TofuCommand : public Command {
public:
TofuCommand(Chef* chef) : chef_(chef) {}
void execute() override { chef_->cookTofu(); }
void undo() override { chef_->cancelTofu(); }
private:
Chef* chef_;
};
// 服务员
class Waiter {
public:
int takeOrder(std::unique_ptr<Command> cmd) {
int id = nextId_++;
orders_[id] = { std::move(cmd), OrderStatus::Pending };
orderQueue_.push_back(id);
std::cout << "服务员:记录订单编号 " << id << "\n";
cookNextIfIdle();
return id;
}
void cancelOrder(int id) {
auto it = orders_.find(id);
if (it != orders_.end()) {
if (it->second.status == OrderStatus::Pending) {
it->second.command->undo();
orderQueue_.erase(
std::remove(orderQueue_.begin(), orderQueue_.end(), id),
orderQueue_.end()
);
orders_.erase(it);
} else {
std::cout << "订单 " << id << " 正在做或已完成,无法撤销。\n";
}
} else {
std::cout << "没有找到编号为 " << id << " 的订单。\n";
}
}
private:
struct OrderItem {
std::unique_ptr<Command> command;
OrderStatus status;
};
void cookNextIfIdle() {
if (isCooking_ || orderQueue_.empty()) return;
isCooking_ = true;
while (!orderQueue_.empty()) {
int id = orderQueue_.front();
orderQueue_.pop_front();
auto it = orders_.find(id);
if (it != orders_.end()) {
it->second.status = OrderStatus::Cooking;
it->second.command->execute();
it->second.status = OrderStatus::Done;
orders_.erase(it);
}
}
isCooking_ = false;
std::cout << "厨师:所有订单已完成。\n";
}
int nextId_ = 1;
bool isCooking_ = false;
std::map<int, OrderItem> orders_;
std::deque<int> orderQueue_;
};
// 测试
int main() {
Chef chef;
Waiter waiter;
int o1 = waiter.takeOrder(std::make_unique<FishCommand>(&chef));
int o2 = waiter.takeOrder(std::make_unique<TofuCommand>(&chef));
int o3 = waiter.takeOrder(std::make_unique<FishCommand>(&chef));
waiter.cancelOrder(o2); // 可以取消豆腐,只要它还没开始做
int o4 = waiter.takeOrder(std::make_unique<TofuCommand>(&chef)); // 新下单豆腐
return 0;
}
五、总结
通过这个餐厅点餐示例,我们可以看到命令模式在实际开发中的几个优势:
-
解耦请求和执行:服务员只负责接单和管理订单,厨师专注于烹饪逻辑,二者职责清晰分明。
-
支持撤销与延迟操作:每道菜都是独立的命令对象,未开始的菜可以随时取消,而正在烹饪的菜则安全执行。
-
扩展性强:增加新菜品只需创建新的命令类,无需修改服务员或厨师核心逻辑,符合开闭原则。
-
维护方便:通过订单状态管理和队列处理,烹饪顺序和撤销逻辑清晰可控。
这个示例不仅清楚地展示了命令模式的用法,也贴合实际餐厅点餐流程,对于想在 C++ 项目中灵活运用设计模式的开发者,很有参考意义。