转自:http://blog.csdn.net/WillLee312/archive/2009/08/15/4450265.aspx
命令(Command)
声明:本文可用于个人学习、参考,未经本人授权严禁用于商业用途。有朋友转载或引用本文请注明作者和出处,谢谢!!
1 模式简介
经理:喂,小丽呀,这儿有封信,请帮我尽快寄出去…
秘书:好的,经理,我马上去办!
秘书拿着这封信,到一家邮局将信寄了出去。邮局最终也将信投递到了收件人手中。
这是一个很常见的生活工作场景,不过这里面:经理、秘书和邮局,三者在不知不觉间,演绎了一出面向对象设计模式:Command模式。Command模式是我打算讲的第一个行为型设计模式。
Command模式的内容大致如此:
将一个请求封装成一个对象(Command对象),并赋予该对象一个执行接口。Command对象在执行命令时,并不一定自己切身做这件事,而是将请求转发给另一个真正做这件事情的对象(Receiver对象),由Receiver对象最终完成请求操作。Command模式使得请求发起者和请求接受者之间解除耦合。
对象来对象去的,比较抽象,想想秘书小丽就好多了:经理需要发起一个请求(发送一封信),他找到了小丽(Command对象),交给她去做这件事。小丽将请求交给邮局(Receiver对象)去最终完成信投递任务。在整个过程中,经理认为执行其命令的是小丽,他不关心也不用知道是哪家邮局最终投递的这封信,即使是小丽自己跑腿儿将信交给收件人,经理也不用关心。
这就是Command模式的魅力:经理多省心呀,^^(大家都想当命令发起者,呵呵)
2 模式结构
Command模式结构如图1所示。
图1 Command模式结构
参与模式的角色,在前面的介绍中,我们已经熟悉了:
1. 请求发起者Invoker
经理,请求的发起者。为了完成一个请求,Invoker调用Command对象的Execute()接口。在Invoker眼里,Command对象就是请求的执行者。
2. 请求对象Command/ConcreteCommand
会有各式各样的请求(这一点,作为秘书的小丽体会最深),“投递一封信”、“去买份盒饭”、“约一下XXX经理会面”……,为了不让Invoker费心,仅向Invoker暴露抽象Command对象,各具体命令对象从Command对象继承。
3. 请求接收者Receiver
请求的真正接收者、执行者。真正干活的人~
4. 客户Client:
这里的Client,他切实的了解谁是请求对象,谁是干活的人,在程序中,其实就是我们程序员自己,最脏最累的活都是我们干,-__-。Client理解整个Command模式,他负责创建ConcreteCommand对象,并用合适的Receiver对象装配它。
Command模式工作开展方式如图2所示。
图2 Command模式序列图
3 核心思想
将请求封装成一个Command对象,并对外提供一个执行接口:Execute()。调用者Invoker调用Command::Execute()执行请求时,Command将请求转交给Receiver去完成,Receiver最终完成请求操作。
Invoker仅面向抽象Command,不关心ConcreteCommand。后续有新的命令扩展时,Invoker不需要任何改变。他关心的仅是:执行,执行,执行……
4 实现关键点
1. Command和Receiver职责分配
Command对象和Receiver对象,在一个请求执行中的职责,Command模式没有严格的要求。在上面的举例中,Command都是把请求透传给Receiver,由Receiver去完成。而在实际应用中,二者的职责有三种分配方案:
a. Command透传请求给Receiver,自己什么都不做;
b. Command自己将事情全部搞定,根本用不着其他的Receiver;
c. Command和Receiver各做一部分。
根据实际应用需要,可以灵活的选择。
其实,在b模式下,Command模式就演变成简单的对象调用,Invoker调用一个对象的接口,对象完成相应操作,就这么简单,哪里像一个设计模式哦,^^
2. 支持redo和undo
发起一个请求,一般还会想到另外两条:redo和undo。在一个严谨、有序的事务设计中,一般都会涉及redo和undo操作(比如Word的Ctrl+Z…)。
支持undo操作(我们俗称的回滚操作),要求Command对象(ConcreteCommand)能够记忆额外的状态信息,这样才可以在undo的时候,恢复到操作之前的状态,也就是状态缓存。
redo和undo的实现根据应用场景的不同,复杂性也不同。如果仅需要支持一次undo操作,那么存储上一次的Command就行了。如果要支持多级undo,可能需要一个历史Command列表。支持的undo级别越高,实现也就越复杂,这里不再深入讨论,感兴趣的朋友可以自己去研究。
3. Template的应用
对于一些简单的命令(不支持undo和参数),我们可以用C++中的模板来定义ConcreteCommand,而不必为每一种Receiver都定义一个ConcreteCommand。比如:
template <typename Receiver>
class ConcreteCommand : public Command
{
public:
// 定义Action为Receiver的函数指针
typedef void (Receiver::* Action)();
public:
ConcreteCommand(Receiver *r, Action a)
: m_receiver(r), m_action(a)
{
}
virtual void Execute();
private:
Action m_action;
Receiver * m_receiver;
};
template <typename Receiver>
void ConcreteCommand<Receiver>::Execute()
{
(m_reciever->(*m_action))();
}
这样,为一个新Receiver定义一个ConcreteCommand就很方便:
MyReceiver *pMyReceiver = new MyReceiver;
……
Command *pCommand = new ConcreteCommand<MyReceiver>(pMyReceiver, &MyReceiver::Action);
……
pCommand->Execute();
……
5 相关设计模式
Command需要支持undo操作时,可以使用Memento模式备份对象的状态,以便后续undo时恢复;
Command模式和Adapter模式存在某种相似性,Command将请求委托给Receiver去做,而Adapter则将请求委托给Adaptee去做,二者结构基本相同。区别在于Command着眼于命令的执行,请求发起者和接受者之间解耦,而Adapter着眼于如何重用已存在对象的实现去响应接口要求不一致的客户请求。此外,Command模式中,Receiver不是必须的,而在Adapter模式中,Adaptee是必须的,否则也不用适配了。
6 设计样例
在通信领域,网元和网元之间的消息交互,就是很好的例子。
网元之间(比如SP和SMSC之间)的消息交互,牵涉到消息的发送方和接收方,发送方,可以认为是这里的Invoker,而消息本身,则视作Command对象(后来想想,也不太贴切,一般消息对象仅包括一些业务信息,并不提供执行操作,那么我们就把消息发送操作+消息,视作Command,^^),消息接收方可视作Receiver。一条消息代表一个请求,发送方将消息发送出去(执行Command::Execute),它并不了解消息接收方,它只知道发送了这条消息,就能达到它想要的功能效果。接收方负责真正消息解析,并按照消息类型执行真正的操作。
这是在架构设计层次的Command模式应用,更多的,还是代码模块级的应用。发起请求,并执行请求,发起者和接受者之间,Command横刀立马,横插一杠子,使得两者解耦。具体样例,限于篇幅,这里就不举了。