设计模式系列(九)命令模式(Command Pattern)
命令模式是将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象,命令模式也支持可撤销的操作。命令模式将行为请求者与行为执行者解耦,从而实现了松耦合的原则。例如:去餐厅点餐的时候,顾客只负责点餐,然后交给服务员,服务员将订单交给厨师去做,最后顾客开始享用美味的饭菜,但是整个过程顾客只关心自己点了什么,而不关心餐厅到底是按照怎样的流程和方法做出这些菜的。再比如:遥控器上有很多按钮,每个按钮都相当于一个命令或者多个命令,当我们按下开机的时候,我们只关心电视是否开机了,而不关心也不知道到底是怎么开机的。这些例子中,顾客点菜以及用遥控器的人都是只请求一些行为,而具体的行为执行则是由其他执行的。
命令模式中通常存在以下5个角色:
(1)Command角色:这个角色是接口或者抽象类,一般只有一个execute()函数留给子类去实现,有时候支持撤销操作的话会加上undo()函数,这个角色是所有具体命令的父类或者说是一个命令接口,并没有任何具体实现;
(2)ConcreteCommand角色:这个角色是抽象命令角色的具体实现类,实现其中的execute()函数,比如说在遥控器上的开机命令、关机命令等,这个具体命令类中,一般都需要有一个Receiver,用来执行真正的命令,再比如餐厅的订单就是命令;
(3)Receiver角色:接收者,真正执行命令的对象,任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能,比如餐厅的厨师就是接收顾客的命令去做菜;
(4)Invoker角色:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口,例如餐厅的服务员就是接收顾客的命令然后使用ConcreteCommand对象交给厨师,再比如遥控器本身就是一个Invoker;
(5)Client角色:创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行,其创建一组命令然后交给Invoker去触发,最终通过ConcreteCommand让Receiver去执行。
上面角色的大致关系是:Client创建一个ConcreteCommand对象并指定他的Receiver对象;然后某个Invoker对象存储该ConcreteCommand对象;该Invoker通过调用Command对象的Execute操作来提交一个请求。若该命令是可撤销的,ConcreteCommand就在执行Execute操作之前存储当前状态以用于取消该命令;ConcreteCommand对象对调用它的Receiver的一些操作以执行该请求。
命令模式的优点是:
(1)降低对象之间的耦合度;
(2)新的命令可以很容易地加入到系统中;
(3)可以比较容易地设计一个组合命令;
(4)调用同一方法实现不同的功能。
命令模式的缺点是:
由于命令模式需要创建许许多多的具体命令类,每一个具体的命令都要创建一个对应的命令类,所以会导致系统中存在大量的具体命令类,使得系统对这个具体命令类的管理比较麻烦。比如:电视机遥控器上很多按钮,每个按钮都对应一个具体命令类,遥控器上有很多按钮,从而就需要定义很多具体的命令类。
下面我们来看一个例子,这个例子是关于类似于遥控器的一个控制器,该例由三个文件组成,依次是:CommandPattern.h、CommandPattern.cpp、CommandPatternTest.cpp。
// 命令模式
// CommandPattern.h文件
#ifndef COMMAND
#define COMMAND
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
using std::string;
using std::cout;
using std::endl;
using std::vector;
// 命令按钮个数
const int g_num = 7;
// 命令模式中的命令角色的抽象类
class Command
{
public:
Command(){}
virtual ~Command(){}
virtual void execute() = 0;
virtual void undo() = 0;
};
// 一个用来初始化命令的具体命令类
class NoCommand : public Command
{
void execute(){}
void undo(){}
};
// 命令模式中的invoker
class RemoteControl
{
public:
RemoteControl()
{
for (int i = 0; i < g_num; i++)
{
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
undoCommand = new NoCommand();
}
~RemoteControl()
{
for (int i = 0; i < g_num; i++)
{
if (onCommands[i] != NULL && typeid(*onCommands[i]) == typeid(NoCommand))
{
delete onCommands[i];
onCommands[i] = NULL;
}
if (offCommands[i] != NULL && typeid(*offCommands[i]) == typeid(NoCommand))
{
delete offCommands[i];
offCommands[i] = NULL;
}
}
if (undoCommand != NULL && typeid(*undoCommand) == typeid(NoCommand))
{
delete undoCommand;
undoCommand = NULL;
}
}
void setCommand(int slot, Command* onCommand, Command* offCommand);
void onButtonWasPushed(int slot);
void offButtonWasPushed(int slot);
void undoButtonWasPushed();
string toString();
private:
Command* onCommands[g_num];
Command* offCommands[g_num];
Command* undoCommand;
};
// 命令模式中的Reciver
class Light
{
public:
Light(string location) {
this->location = location;
}
void on();
void off();
void dim(int level);
int getLevel();
private:
string location;
int level;
};
class Stereo
{
public:
Stereo(string location)
{
this->location = location;
}
void on();
void off();
void setCD();
void setDVD();
void setRadio();
void setVolume(int volume);
private:
string location;
};
class CeilingFan
{
public:
static const int HIGH;
static const int MEDIUM;
static const int LOW;
static const int OFF;
CeilingFan(string location)
{
this->location = location;
}
void high();
void medium();
void low();
void off();
int getSpeed();
private:
string location;
int speed;
};
// 命令模式中具体的命令类
class LightOnCommand : public Command
{
public:
LightOnCommand(Light& light) : light(light){}
void execute();
void undo();
private:
Light& light;
};
class LightOffCommand : public Command
{
public:
LightOffCommand(Light& light) : light(light){}
void execute();
void undo();
private:
Light& light;
};
class LivingroomLightOnCommand : public Command
{
public:
LivingroomLightOnCommand(Light& light) : light(light){}
void execute();
void undo();
private:
Light& light;
};
class LivingroomLightOffCommand : public Command
{
public:
LivingroomLightOffCommand(Light& light) : light(light){}
void execute();
void undo();
private:
Light& light;
};
class StereoOnCommand : public Command
{
public:
StereoOnCommand(Stereo& stereo) : stereo(stereo){}
void execute();
void undo();
private:
Stereo& stereo;
};
class StereoOffCommand : public Command
{
public:
StereoOffCommand(Stereo& stereo) : stereo(stereo){}
void execute();
void undo();
private:
Stereo& stereo;
};
class StereoOnWithCDCommand : public Command
{
public:
StereoOnWithCDCommand(Stereo& stereo) : stereo(stereo){}
void execute();
void undo();
private:
Stereo& stereo;
};
class CeilingFanHighCommand : public Command
{
public:
CeilingFanHighCommand(CeilingFan& ceilingFan) : ceilingFan(ceilingFan){}
void execute();
void undo();
private:
CeilingFan& ceilingFan;
int prevSpeed;
};
class CeilingFanMediumCommand : public Command
{
public:
CeilingFanMediumCommand(CeilingFan& ceilingFan) : ceilingFan(ceilingFan){}
void execute();
void undo();
private:
CeilingFan& ceilingFan;
int prevSpeed;
};
class CeilingFanOffCommand : public Command
{
public:
CeilingFanOffCommand(CeilingFan& ceilingFan) : ceilingFan(ceilingFan){}
void execute();
void undo();
private:
CeilingFan& ceilingFan;
int prevSpeed;
};
// 这个是命令模式中常用的类:命令宏,用来执行一组命令
class MacroCommand : public Command
{
public:
MacroCommand(vector<Command*> commands)
{
this->commands = commands;
}
void execute();
void undo();
private:
vector<Command*> commands;
};
#endif
// CommandPattern.cpp文件
#include "CommandPattern.h"
// 命令模式中的invoker
void RemoteControl::setCommand(int slot, Command* onCommand, Command* offCommand)
{
if (onCommands[slot] != NULL && typeid(*onCommands[slot]) == typeid(NoCommand))
{
delete onCommands[slot];
}
if (offCommands[slot] != NULL && typeid(*offCommands[slot]) == typeid(NoCommand))
{
delete offCommands[slot];
}
onCommands [slot] = onCommand;
offCommands[slot] = offCommand;
}
void RemoteControl::onButtonWasPushed(int slot)
{
onCommands[slot]->execute();
if (undoCommand != NULL && typeid(*undoCommand) == typeid(NoCommand))
{
delete undoCommand;
}
undoCommand = onCommands[slot];
}
void RemoteControl::offButtonWasPushed(int slot)
{
offCommands[slot]->execute();
if (undoCommand != NULL && typeid(*undoCommand) == typeid(NoCommand))
{
delete undoCommand;
}
undoCommand = onCommands[slot];
}
void RemoteControl::undoButtonWasPushed()
{
undoCommand->undo();
}
string RemoteControl::toString()
{
string stringBuff;
char buf[2];
stringBuff += "\n------ Remote Control -------\n";
for (int i = 0; i < g_num; i++)
{
stringBuff += "[slot ";
sprintf_s(buf, "%d", i);
stringBuff += buf;
stringBuff += "] ";
stringBuff += typeid(*onCommands[i]).name();
stringBuff += " ";
stringBuff += typeid(*offCommands[i]).name();
stringBuff += "\n";
}
stringBuff += "[undo] ";
stringBuff += typeid(*undoCommand).name();
stringBuff += "\n";
return stringBuff;
}
// 命令模式中的Reciver
// Light
void Light::on()
{
level = 100;
cout << "Light is on" << endl;
}
void Light::off() {
level = 0;
cout << "Light is off" << endl;
}
void Light::dim(int level)
{
this->level = level;
if (level == 0)
{
off();
}
else
{
cout << "Light is dimmed to " << level << "%" << endl;
}
}
int Light::getLevel()
{
return level;
}
// Stereo
void Stereo::on()
{
cout << location << " stereo is on" << endl;
}
void Stereo::off()
{
cout << location << " stereo is off" << endl;
}
void Stereo::setCD()
{
cout << location << " stereo is set for CD input" << endl;
}
void Stereo::setDVD()
{
cout << location << " stereo is set for DVD input" << endl;
}
void Stereo::setRadio()
{
cout << location << " stereo is set for Radio" << endl;
}
void Stereo::setVolume(int volume)
{
// code to set the volume
// valid range: 1-11 (after all 11 is better than 10, right?)
cout << location << " Stereo volume set to " << volume << endl;
}
// CeilingFan
const int CeilingFan::HIGH = 3;
const int CeilingFan::MEDIUM = 2;
const int CeilingFan::LOW = 1;
const int CeilingFan::OFF = 0;
void CeilingFan::high()
{
// turns the ceiling fan on to high
speed = HIGH;
cout << location + " ceiling fan is on high" << endl;
}
void CeilingFan::medium()
{
// turns the ceiling fan on to medium
speed = MEDIUM;
cout << location + " ceiling fan is on medium" << endl;
}
void CeilingFan::low()
{
// turns the ceiling fan on to low
speed = LOW;
cout << location + " ceiling fan is on low" << endl;
}
void CeilingFan::off()
{
// turns the ceiling fan off
speed = OFF;
cout << location + " ceiling fan is off" << endl;
}
int CeilingFan::getSpeed()
{
return speed;
}
// 命令模式中具体的命令类
// LightOnCommand
void LightOnCommand::execute()
{
light.on();
}
void LightOnCommand::undo()
{
light.off();
}
// LightOffCommand
void LightOffCommand::execute()
{
light.off();
}
void LightOffCommand::undo()
{
light.on();
}
// LivingroomLightOnCommand
void LivingroomLightOnCommand::execute()
{
light.on();
}
void LivingroomLightOnCommand::undo()
{
light.off();
}
// LivingroomLightOffCommand
void LivingroomLightOffCommand::execute()
{
light.off();
}
void LivingroomLightOffCommand::undo()
{
light.on();
}
// StereoOnCommand
void StereoOnCommand::execute()
{
stereo.on();
}
void StereoOnCommand::undo()
{
stereo.off();
}
// StereoOffCommand
void StereoOffCommand::execute()
{
stereo.off();
}
void StereoOffCommand::undo()
{
stereo.on();
}
// StereoOnWithCDCommand
void StereoOnWithCDCommand::execute()
{
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
void StereoOnWithCDCommand::undo()
{
stereo.off();
}
// CeilingFanHighCommand
void CeilingFanHighCommand::execute()
{
prevSpeed = ceilingFan.getSpeed();
ceilingFan.high();
}
void CeilingFanHighCommand::undo()
{
switch (prevSpeed)
{
case CeilingFan::HIGH: ceilingFan.high(); break;
case CeilingFan::MEDIUM: ceilingFan.medium(); break;
case CeilingFan::LOW: ceilingFan.low(); break;
default: ceilingFan.off(); break;
}
}
// CeilingFanMediumCommand
void CeilingFanMediumCommand::execute()
{
prevSpeed = ceilingFan.getSpeed();
ceilingFan.medium();
}
void CeilingFanMediumCommand::undo()
{
switch (prevSpeed)
{
case CeilingFan::HIGH: ceilingFan.high(); break;
case CeilingFan::MEDIUM: ceilingFan.medium(); break;
case CeilingFan::LOW: ceilingFan.low(); break;
default: ceilingFan.off(); break;
}
}
// CeilingFanOffCommand
void CeilingFanOffCommand::execute()
{
prevSpeed = ceilingFan.getSpeed();
ceilingFan.off();
}
void CeilingFanOffCommand::undo()
{
switch (prevSpeed)
{
case CeilingFan::HIGH: ceilingFan.high(); break;
case CeilingFan::MEDIUM: ceilingFan.medium(); break;
case CeilingFan::LOW: ceilingFan.low(); break;
default: ceilingFan.off(); break;
}
}
// 这个是命令模式中常用的类:命令宏,用来执行一组命令
void MacroCommand::execute()
{
for (size_t i = 0; i < commands.size(); i++)
{
commands[i]->execute();
}
}
void MacroCommand::undo()
{
for (size_t i = 0; i < commands.size(); i++)
{
commands[i]->undo();
}
}
// CommandPatternTest.cpp文件
#include "CommandPattern.h"
void main()
{
RemoteControl remoteControl;
Light light("Living Room");
Stereo stereo("Living Room");
CeilingFan ceilingFan("Living Room");
LightOnCommand on1(light);
LivingroomLightOnCommand on2(light);
StereoOnCommand on3(stereo);
CeilingFanHighCommand on4(ceilingFan);
CeilingFanMediumCommand on5(ceilingFan);
LightOffCommand off1(light);
LivingroomLightOffCommand off2(light);
StereoOffCommand off3(stereo);
CeilingFanOffCommand off4(ceilingFan);
vector<Command*> onCommands;
vector<Command*> offCommands;
onCommands.push_back(&on1);
onCommands.push_back(&on2);
onCommands.push_back(&on3);
onCommands.push_back(&on4);
onCommands.push_back(&on5);
offCommands.push_back(&off1);
offCommands.push_back(&off2);
offCommands.push_back(&off3);
offCommands.push_back(&off4);
MacroCommand partyOnMacro(onCommands);
MacroCommand partyOffMacro(offCommands);
remoteControl.setCommand(0, &partyOnMacro, &partyOffMacro);
cout << remoteControl.toString() << endl;
cout << "--- Pushing Macro On---" << endl;
remoteControl.onButtonWasPushed(0);
cout << "--- Pushing Macro Off---" << endl;
remoteControl.offButtonWasPushed(0);
}
该例的运行结果如图1所示,UML类图如图2所示。

图1 运行结果
图2 UML类图
我们来看一下上述例子中各个类与命令模式中的角色的对应关系:
(1)Command角色:对应于该例中的Command类,由于该例支持撤销操作,所以其中包含了execute()和undo()两个函数,该例中的撤销操作是撤销上一次执行的操作;
(2)ConcreteCommand角色:NoCommand、LightOnCommand等后缀为Command的类都是具体命令角色,其中包含了具体的命令执行者,即Receiver;
(3)Receiver角色:其中的Light类、Stereo类以及CeilingFan类都是接收者,其中每个类都定义了各自的行为,比如on()、off()等函数,而且都是变化的;
(4)Invoker角色:RemoteControl类,其中持有很多命令对象,并有相应的操作来触发执行,注意这个类中有一个setCommand()函数就是用来将Client创建的具体命令对象保存在该类中,用于触发行为;
(5)Client角色:该例中的这个角色就是最后一个源文件,即测试用例,其中就是创建具体的命令及其接收者的程序。
细心的朋友一定发现其中有一个MacroCommand类,这个类是用来执行一组命令,即所谓的Party模式,这个类中包含一组命令,然后执行时依次执行每个命令,即命令宏,这个是十分常见的应用。
至于C++的实现程序中需要注意的是typeid的使用,它是RTTI(运行时类型检查)的一种应用,我们主要使用它来判断类型具体的子类类型以及显示。
总之,命令模式并不是十分复杂,但是却有很多细节需要注意。通常,命令模式也可以用于日志记录与恢复、事务处理与事务回滚等,具体的应用还是需要从实践中慢慢领悟。