命令模式
命令(Command)模式是一种行为型模式,其实现有些烦琐,适用于一些比较专用的场合。本章首先通过一个采用命令模式编写的范例引入命令模式的概念,然后具体阐述命令模式适用的场景,达到让读者对该模式活学活用的目的。在本章的最后,还将阐述命令模式的特点以及一些值得深入思考的话题。
9.1 通过一个范例引出命令模式代码编写方法
设想来到一家饭馆,点两个菜吃——红烧鱼和锅包肉,编写一下这个饭馆的相关代码:
// 厨师类
class Cook {
public:
// 做红烧鱼
void cook_fish() {
cout << " 做一盘红烧鱼菜品" << endl;
}
// 做锅包肉
void cook_meat() {
cout << " 做一盘锅包肉菜品" << endl;
}
// 做其他各种菜品 …
};
在 main
主函数中让厨师做菜:
Cook* pcook = new Cook();
pcook->cook_fish();
pcook->cook_meat();
// 释放资源
delete pcook;
引入服务员类,实现命令模式。首先创建命令对应的抽象父类 Command
:
// 厨师做的每样菜品对应的抽象类
class Command {
public:
Command(Cook* pcook) {
m_pcook = pcook;
}
// 作父类时析构函数应该为虚函数
virtual ~Command() {}
virtual void Execute() = 0;
protected:
Cook* m_pcook; // 子类需要访问
};
针对厨师能做的菜实现具体的命令子类:
// 做红烧鱼菜品命令(顾客下的红烧鱼菜品便签)
class CommandFish : public Command {
public:
CommandFish(Cook* pcook) : Command(pcook) {}
virtual void Execute() {
m_pcook->cook_fish();
}
};
// 做锅包肉菜品命令(顾客下的锅包肉菜品便签)
class CommandMeat : public Command {
public:
CommandMeat(Cook* pcook) : Command(pcook) {}
virtual void Execute() {
m_pcook->cook_meat();
}
};
在 main
主函数中使用命令子类:
Cook cook;
Command* pcmd1 = new CommandFish(&cook);
pcmd1->Execute(); // 做红烧鱼
Command* pcmd2 = new CommandMeat(&cook);
pcmd2->Execute(); // 做锅包肉
// 释放资源
delete pcmd1;
delete pcmd2;
引入服务员类 Waiter
:
// 服务员类
class Waiter {
public:
void SetCommand(Command* pcommand) {
m_pcommand = pcommand;
}
void Notify() {
m_pcommand->Execute();
}
private:
Command* m_pcommand;
};
在 main
主函数中使用服务员类:
Cook cook;
Waiter* pwaiter = new Waiter();
Command* pcmd1 = new CommandFish(&cook);
pwaiter->SetCommand(pcmd1);
pwaiter->Notify(); // 做红烧鱼
Command* pcmd2 = new CommandMeat(&cook);
pwaiter->SetCommand(pcmd2);
pwaiter->Notify(); // 做锅包肉
// 释放资源
delete pcmd1;
delete pcmd2;
delete pwaiter;
修改服务员类以支持多道菜品:
class Waiter {
public:
// 将顾客的便签增加到便签列表中
void AddCommand(Command* pcommand) {
m_commlist.push_back(pcommand);
}
void DelCommand(Command* pcommand) // 如果顾客想撤单则将便签从列表中删除
{
m_commlist.remove(pcommand);
}
void Notify() // 服务员将所有便签一次性交到厨师手里让厨师开始按顺序做菜
{
for (auto iter = m_commlist.begin(); iter != m_commlist.end(); ++iter) {
(*iter)->Execute();
}
}
private:
std::list<Command*> m_commlist; // 菜品列表
};
在 main
主函数中使用修改后的服务员类:
Cook cook;
Command* pcmd1 = new CommandFish(&cook);
Command* pcmd2 = new CommandMeat(&cook);
Waiter* pwaiter = new Waiter();
pwaiter->AddCommand(pcmd1);
pwaiter->AddCommand(pcmd2);
pwaiter->Notify(); // 服务员一次性通知厨师做多道菜
// 释放资源
delete pcmd1;
delete pcmd2;
delete pwaiter;
9.2 引入命令模式
命令模式的 UML 图包含 5 种角色:
(1) Receiver(接收者类):如 Cook
类,提供业务处理接口。
(2) Invoker(调用者类):如 Waiter
类,通过命令对象执行请求。
(3) Command(抽象命令类):如 Command
类,声明执行操作的接口。
(4) ConcreteCommand(具体命令类):如 CommandFish
、CommandMeat
类,实现执行请求的方法。
(5) Client(客户端):创建命令对象,设定接收者,驱动调用者执行动作。
命令模式的实现意图:将请求封装为对象,以便通过参数传递,支持排队执行、日志记录、可撤销操作等。
9.3 命令模式用途研究
9.3.1 改造范例增加对象使用时的独立性
调整 Command
类析构函数:
virtual ~Command() {
if (m_pcook != nullptr) {
delete m_pcook;
m_pcook = nullptr;
}
}
引入实习服务员类 Traineewaiter
:
// 实习服务员类
class Traineewaiter {
public:
Traineewaiter(Command* pcommand) : m_pcommand(pcommand) {} // 构造函数
void Notify() {
m_pcommand->Execute();
}
~Traineewaiter() {
if (m_pcommand != nullptr) {
delete m_pcommand;
m_pcommand = nullptr;
}
}
private:
Command* m_pcommand;
};
在 main
主函数中使用实习服务员类:
Traineewaiter* pwaitersx1 = new Traineewaiter(new CommandFish(new Cook()));
pwaitersx1->Notify(); // 做红烧鱼
Traineewaiter* pwaitersx2 = new Traineewaiter(new CommandMeat(new Cook()));
pwaitersx2->Notify(); // 做锅包肉
// 释放资源
delete pwaitersx1;
delete pwaitersx2;
9.3.2 命令模式使用场景与特点总结
适用场景:
- 绘图软件:实现撤销、重做、日志记录等功能。
- 遥控器控制:解耦遥控器与家用电器,灵活控制。
- 任务调度:实现任务的定期调度执行。
- 游戏系统:实现时光倒流、情景回放等功能。
特点:
- 解耦:请求发送者和接收者解耦。
- 开闭原则:通过增加
Command
子类支持功能扩充。 - 子类较多:实现时可能引入较多
Command
子类。
相关思考:
- 命令对象与回调函数:命令模式是回调机制的面向对象替代品。
- 极端情形:允许不引入调用者类或接收者类,确保命令对象找到接收者即可。
- 与可调用对象比较:可调用对象性能更高,与模板结合更灵活,合适场合优先使用可调用对象。