1. 情景与意图
如果学习过Linux操作系统的同学会知道,使用Linux操作系统的同学一般是不会用鼠标点来点去,虽然是带有桌面的,我们通常是使用命令的方式。说到这,无耻的引流来了,如果有对Linux操作系统感兴趣的话,欢迎关注【Linux操作系统学习系列】。
话说回来,如果说上面的流程用下面的代码来实现:
// 伪代码
void lsFunc(){}
void cdFunc(){}
void pwdFunc(){}
void command(int cmd) {
switch(cmd)
case 1: lsFunc()
break;
case 2: cdFunc()
break;
case 3: pwdFunc()
break;
}
此时就会有问题,我们新增 grep 、top、netstat等命令,就要修改 command()这个方法了,会导致command这个方法越来越臃肿。这里面的问题就是调用和实现紧耦合,在某些场景下,我们应该将调用和实现解耦,怎么实现这种设计呢?——命令模式
2. 命令模式
将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。
在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
怎么解释其实都不好理解,下面我们就用命令模式来实现这个shell,通过代码学习命令模式。
3. Shell
先定义我们的shell命令。这个shell的设计其实也不好,更好的是,我们继续将shell进行抽象,这样在后面新增命令的时候不用再更改这个类,方便扩展,但是这里不这样实现,为了更方便的阅读与演示。
class DPShell {
public:
void ls(std::string arg);
void cd(std::string arg);
void pwd();
};
然后建立一个中间调用类。主要是为了将上面的shell类的每一个方法进行隔离,为的是将第一节演示的switch的判断语句进行拆解,这样就不用在一个方法里面进行调用。
class DPCommand {
protected:
string _info;
public:
virtual string info() = 0;
virtual void execute() = 0;
};
class DPLsCommad :public DPCommand {
// 这个shell其实可以抽离到父类
DPShell _shell;
std::string _arg;
public:
DPLsCommad(std::string arg = "");
virtual string info();
virtual void execute();
};
class DPCdCommad :public DPCommand {
/* 省略 */
};
class DPPwdCommad :public DPCommand {
/* 省略 */
};
实现其中一个吧,其余都是一样的
DPLsCommad::DPLsCommad(std::string arg)
:_arg(arg) {
_info = string("ls") += arg;
}
string DPLsCommad::info() {
return _info;
}
void DPLsCommad::execute() {
_shell.ls(_arg);
然后我们实现一个核心调用类。主要是管理我们命令的调用
class DPInvoker {
vector<DPCommand*> _comList;
public:
void execute(DPCommand* cmd);
void showHistoryCommand();
void executeCmdWithIndex(size_t index);
};
void DPInvoker::execute(DPCommand* cmd) {
if (cmd != nullptr) {
cmd->execute();
_comList.push_back(cmd);
}
}
void DPInvoker::showHistoryCommand() {
for (auto cmd : _comList) {
std::cout << cmd->info() << std::endl;
}
}
void DPInvoker::executeCmdWithIndex(size_t index) {
if (index < _comList.size()) {
execute(_comList[index]);
}
}
看一下mian 函数吧。可以看到shell其实已经被隐藏了,我们也没有一个臃肿的switch。这就是命令模式的好处。
int main() {
DPCommand* lsCmd = new DPLsCommad();
DPInvoker invoker;
invoker.execute(lsCmd);
invoker.showHistoryCommand();
return 0;
}
4. 总结
再看看概念:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。请求就是说我们将对象的行为封装成命令,比如shell对象的ls方法,封装成DPLsCommad对象。最简单的理解就是把这个命令对象当成一个函数指针,好处就是更加的灵活。
命令模式将调用与实现进行了松耦合,降低了系统耦合度,新的命令也可以很容易添加到系统中去。缺点也很明显,使用命令模式可能会导致某些系统有过多的具体命令类。
博文省略了一些代码,详细代码见C++实现命令模式代码:【命令模式C++源码】