深入浅出设计模式--命令模式
- 一、背景
- 二、问题
- 三、解决方案
- 四、试用场景总结
- 五、后记
一、背景
命令模式是一种行为设计模式,它可以将用户的命令请求转化为一个包含有相关参数信息的对象,命令的发送者不需要知道接收者是如何处理这条命令,多个功能入口可以发送同一命令,避免多处多次实现相同功能的冗余代码。另外可以对命令进行延迟处理,或放入队列或栈中,支持命令回撤。
二、问题
在使用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;
};
四、试用场景总结
- 若多个不同操作对应同一处理结果,那么可以使用命令模式。如点击菜单项、点击按钮和ctrl+s进行保存
- 若同一操作在不同的场景下产生不同的结果,在运行时切换已连接的命令,也可以使用命令模式。如用户可以配置菜单项,在点击时触发不同的命令。
- 若需要将操作放入队列中,延迟或计划发送命令,也可以使用命令模式。
- 若要实现操作回滚(撤销)功能,也可以使用命令模式。命令历史记录是一种包含所有已执行命令对象及其相关程序状态备份的栈结构。
- 若要远程执行命令,需要将命令对象序列化,从而能方便地写入文件或数据库中。
五、后记
以上所有内容均为原创,代码已上传至gayhub:
https://github.com/gangster-puppy/Design-Pattern.git