1.意图
将一个请求封装成对象,从而使你可用不同的请求对客户进行参数化,并可以对请求排队或者记录请求日志,以及支持可撤销的操作。
2.适用性
- 需要抽象出待执行的动作以参数化对象。你可以用过程语言中的回调函数来表达这种参数机制。
- 在不同的时刻指定、排列和执行请求。
- 支持取消操作。
- 支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。
- 用构建在原语操作上的高层操作构建一个系统。
3.结构
Command:声明执行操作的接口
Client:创建一个具体命令并设定它的接受者
Invoker:要求该命令执行这个请求
Receiver:知道如何实施与执行一个相关请求的操作,任何类都可能作为一个接收者。
4.效果
Command模式有以下效果:
- Command模式将调用操作的对象与知道如何实现该操作的对象解耦。
- Command与其他对象一样可以被操纵或者扩展。
- 你可以将多个命令装配成一个复合命令。
- 增加新的命令很容易,因为无需修改已有的类
5.实现
实现Command模式时需要考虑以下问题:
1>一个命令对象应达到何种智能程度。命令对象的能力可大可小,一个极端是它仅确定一个接收者和执行该请求的动作,另一个极端是它自己实现所有的功能,根本不需要额外的接受者对象。当需要定义与已有的类无关的命令,或者当没有合适的接受者,或者当一个命令隐式地知道它的接收者时,可以使用后一种极端方法。
2>支持取消和重做。为达到这个目的,command类可能需要存储额外的状态信息,这个转态包括:
- 接收者对象以及它真正执行该请求的各操作。
- 接收者上执行操作的参数
- 如果处理请求的操作会改变接收者对象中的某些值,那么这些值也应该被存储起来。
3>避免取消操作中的错误累积。即一定要保证撤销操作可以正确的恢复对象的状态。
4>使用c++模板。对不能被取消或者不需要参数的命令,我们可以通过模板来实现,这样可以避免为每一种动作和接收者都创建一个子类。
6.代码示例
一下是一个模拟文件操作的命令程序:
#include <iostream>
#include <vector>
#include <list>
using namespace std;
std::string askUserForDocName()
{
cout << "input the document name: ";
std::string docName;
cin >> docName;
return docName;
}
class Document
{
public:
Document(const std::string& name) :_name(name){}
const std::string& getName() { return _name; }
void open()
{
std::cout << "documnet " << _name << " open" << std::endl;
}
void paste()
{
std::cout << "document " << _name << " is pasted to the board" << std::endl;
}
private:
std::string _name;
};
//程序类,用于管理文档
class Application
{
public:
Application(){}
void add(Document *doc)
{
_docments.push_back(doc);
cout << "application add doc: " << doc->getName() << std::endl;
}
private:
std::list<Document *> _docments;
};
class Command
{
public:
virtual ~Command(){}
virtual void execute() = 0;
protected:
Command(){}
};
//打开文档命令,其需要一个Application(应用)作为接受者
class OpenCommand : public Command
{
public:
OpenCommand(Application *app) :_app(app){}
void execute() override
{
std::string name = askUserForDocName();
Document *doc = new Document(name);
_app->add(doc);
doc->open();
}
private:
Application *_app = nullptr;
char *_response = nullptr;
};
//复制文档命令,其需要一个Document(文档)作为接受者
class PasteCommand : public Command
{
public:
PasteCommand(Document *doc) :_doc(doc){}
void execute() override
{
_doc->paste();
}
private:
Document *_doc = nullptr;
};
int main()
{
auto app = new Application();
auto openCmd = new OpenCommand(app);
openCmd->execute();
auto doc = new Document(askUserForDocName());
auto pasteCmd = new PasteCommand(doc);
pasteCmd->execute();
system("PAUSE");
return 0;
}
上述代码测试结果如下:
在上述代码中我们可以看到OpenCommand与PastedCommand有不同的接受者,此时我们可以使用类模板来参数化该命令的接受者,如下:
template<class Receiver>
class SimpleCommand : public Command
{
public:
//定义函数指针以传入命令相应的操作
typedef void (Receiver::* Action)();
SimpleCommand(Receiver *rec, Action action) :_receiver(rec), _action(action){}
void execute() override
{
//“->*”操作符用于成员指针,需留意其用法
(_receiver->*_action)();
}
private:
Action _action;
Receiver *_receiver = nullptr;
};
int main()
{
auto doc = new Document(askUserForDocName());
auto pasteCmd = new SimpleCommand<Document>(doc, &Document::paste);
pasteCmd->execute();
system("PAUSE");
return 0;
}
这样我们就使用一个模板将一个命令以及一个接受者以及此接受者中的一个操作函数绑定在一起,当然这种模板方法只适用于简单命令。
我们也可以定义一个宏命令(组合命令),来管理一个子命令序列,它提供了增加和删除子命令的操作。如下:
class MacroCommand :public Command
{
public:
//此命令不需要接受者,因为其子命令已经包含了接受者
MacroCommand(){}
void addCmd(Command *cmd){ _cmds.push_back(cmd); }
void removeCmd(Command *cmd) { _cmds.remove(cmd); }
void execute()
{
for (auto cmd : _cmds)
cmd->execute();
}
private:
std::list<Command *> _cmds;
};
int main()
{
auto macroCmd = new MacroCommand();
auto doc = new Document(askUserForDocName());
auto pasteCmd1 = new SimpleCommand<Document>(doc, &Document::paste);
doc = new Document(askUserForDocName());
auto pasteCmd2 = new SimpleCommand<Document>(doc, &Document::paste);
macroCmd->addCmd(pasteCmd1);
macroCmd->addCmd(pasteCmd2);
macroCmd->execute();
system("PAUSE");
return 0;
}
运行结果如下:
7 相关模式
Composite(组合)模式:可用来实现宏命令
Memento(备忘录)模式可用来保持某个状态,命令用这一状态来执行撤销操作。