特征
该模式的特征在于将请求封装为对象,从而将行为请求者与行为施行者解耦出来。
目的
行为请求和施行分离开,进行类似事务处理,可还原、重做、重现请求。
详情
假设现有五指棋玩家Actor:
class Actor
{
void play(int pos_x,int pos_y);
void undo();
}
该玩家有两种行为,下棋和悔棋。通常的操作是对于外界(比如键盘)的输入,玩家做出不同反应,即执行对应的成员方法。
void update(int cmd,Actor *player)
{
if(cmd== 0)
{
player->play(...);
}
else if(cmd == 1)
{
player->undo();
}
}
现在玩家在键盘按下数字键1进行悔棋,请问该如何操作?
当然undo方法也可以设置参数,即传入上一次落子的坐标,可达到效果,因此我们可以记录每次落子的坐标。
但这里的例子是五指棋,对于悔棋我们只可以进行一次(两次意味着把对手的也回退了),如果是对于可以连续回退的情景呢?
分析上面的例子,落子和悔棋的行为与玩家紧密相连,是不是可以考虑将行为和执行者分离开呢?对于外界的指令我们就只关注指令本身,将其单独提取出来。
将
指令输入-------->玩家执行
变为:
指令输入-------->指令对象-------->玩家执行
也就是:为每一条指令生成一个对象。
这样的好处是我们可以将随指令传入的参数一起打包,然后将该对象放置于等待队列就行,请求者的任务到这里已经完成。
接下来只需在不断的从等待队列提取指令对象并执行就可以,这属于执行者的工作。
如此一来,请求者和执行者的工作被分离开来,请求者不用等待执行者执行完动作返回TRUE后才能继续下一步,而且指令放在队列中,可被延时执行,这是很有意义的一种思想(例如大量用户同一时刻登录某服务器,这时候就需要排队,将登录请求打包放进等到队列,等待服务器去调度就行)。
简单例子
class Actor
{
void run();
void crouch();
void fire();
}
class Command
{
virtual void excute() = 0;
Actor *actor;
}
class RunCommand:public Command
{
void excute() {actor -> run();}
}
class CrouchCommand:public Command
{
void excute(){actor -> crouch();}
}
class FireCommand:public Command
{
void excute(){actor -> fire();}
}
class Scene()
{
void doSomething(int arg);
void update();
Actor* actor;
std::stack<Command> commandList;
}
void Scene::doSomething(int arg)
{
if(arg == 0)
{
RunCommand command(actor);
commandList.push(command);
}
else if(arg == 1)
{
CrouchCommand command(actor);
commandList.push(command);
}
else
{
FireCommand command(actor);
commandList.push(command);
}
}
void Scene::update()
{
while(!commandList.empty())
{
auto commander = commandList.pop();
commander.excute();
}
}
例子是随意写的,目的只是为了表达想法,不要在意有没有语法错误等等。
它的类图就不画了,直接看下菜鸟教程的图:
这里的ICommand和子类ConcreteCommand就是指令类。
Receiver是具体执行行为的类,对应于例子的Actor。
Invoker对应与上面的Scene类,相当于管理指令和执行者的管理器吧。
Client就当作一个大环境吧,也可以理解为一个管理器。
总结
可能我的理解不到位,欢迎大佬批评指正。
参考:菜鸟教程-命令模式