命令模式是一种行为设计模式,它可以将用户的命令请求转化为一个包含有相关参数信息的对象,命令的发送者不需要知道接收者是如何处理这条命令,多个功能入口可以发送同一命令,避免多处多次实现相同功能的冗余代码。另外可以对命令进行延迟处理,或放入队列或栈中,支持命令回撤。
在使用GUI界面的应用程序时,一般保存功能会有多个入口,比如点击按钮保存、点击菜单项保存和使用键盘ctrl+s保存等,每个入口的位置不同且没有什么关联关系,只是最终实现文档保存功能的代码是一样的。

此时要么将这些操作代码复制粘贴进多个类中,要么就是让菜单项依赖于按钮,显然这两种方式都是不明智的。
一般在这种一对多、多对多的场景下,最好的方式是添加一个中间层,上层负责GUI的交互,下层负责业务逻辑的处理,中间层则将命令请求抽象为一个对象,在上下两层中传递消息数据,该对象可以连接不同的GUI和业务逻辑对象GUI 对象无需了解业务逻辑对象是否获得了请求,也无需了解其对请求进行处理的方式。

1. 该模式的类图如下所示:发送者ICommandSender中包含了命令接口ICommand的指针,ICommand中只有execute虚函数,具体的命令类SaveCommand中包含有命令接收者Document的指针,且重写execute函数。CommandHistory类记录了每条执行了的命令,调用pop弹出命令时可以调用该命令的redo函数(下图中未展示)进行命令撤销。

2. 该模式下各类之间交互的时序图如下:首先生成命令接收者Document,然后生成命令SaveCommand并绑定Document,接下来生成命令发送者Button并绑定SaveCommand,最后Button调用触发函数click让命令对象去执行具体的execute函数。

3. 相关代码实现:
class CommandHistory{
public:
static CommandHistory* GetInstance(){
static CommandHistory cmdHis;
return &cmdHis;
}
void push(ICommand* cmd){m_cmdHisVec.push(cmd);}
void pop(){m_cmdHisVec.pop();}
size_t size(){return m_cmdHisVec.size();}
private:
stack<ICommand*> m_cmdHisVec;
};
#define CMD_HIS CommandHistory::GetInstance()
class ICommandSender{
public:
ICommandSender(){}
~ICommandSender(){}
void setCommand(ICommand* cmd){m_cmd = cmd;}
ICommand* getCommand(){return m_cmd;}
protected:
ICommand* m_cmd;
};
class Button : public ICommandSender{
public:
void click(){
m_cmd->execute("Sent by Button");
CMD_HIS->push(m_cmd);
cout << "Count of Button history command is " << CMD_HIS->size() << endl;
}
};
class Shortcut : public ICommandSender{
public:
void setCommand(const string& key, ICommand* cmd){
m_cmd = cmd;
m_keyCmdMap[key] = cmd;
}
void press(){
m_cmd->execute("Sent by Shortcut");
CMD_HIS->push(m_cmd);
cout << "Count of Shortcut history command is " << CMD_HIS->size() << endl;
}
private:
map<string, ICommand*> m_keyCmdMap;
};
class ICommand{
public:
virtual ~ICommand(){}
virtual void execute(const string& from) = 0;
};
class SaveCommand : public ICommand{
public:
SaveCommand(Document* doc) : m_doc(doc) {}
virtual void execute(const string& from) override{
m_doc->setText(from);
m_doc->save();
}
private:
Document* m_doc;
};
class Document{
public:
void setText(const string& text){
m_text = text;
}
void save(){
cout << m_text << " has been saved" << endl;
}
private:
string m_text;
};
以上所有内容均为原创,代码已上传至gayhub:
https://github.com/gangster-puppy/Design-Pattern.git