1. 概述
有时候必须向某对象提交请求,但并不知道关于被请求的操作或请求的接受者的任何信息。例如,用户界面工具箱包括按钮和菜单这样的对象,它们执行请求响应用户输入。但工具箱不能显示在按钮或菜单中实现该请求,因为只有使用工具箱的应用知道该由哪个对象做哪个操作。而工具箱的设计者无法知道请求的接受者或执行的的操作。
命令模式(Command),将一个请求封装称为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
就我们的例子来说命令模式通过将请求本身变成一个对象来使工具箱对象可向未指定的应用对象提出请求。
2. 结构图
下面是命令模式(Command)的结构图:
- Command:声明执行操作的接口。
- ConcreteCommand:将一个接收者对象绑定于一个动作;调用接收者相应的操作,以实现Execute。
- Client:创建一个具体命令对象并设定它的接收者。
- Invoker:要求该命令执行这个请求。
- Receiver:直到如何实施与执行一个请求相关操作的请求。任何类都可能作为一个接收者。
下面我们展示这些角色时如何交互的:
- Client创建一个ConcreteCommand对象并指定它的Receiver对象。
- 某Invoker对象存储该ConcreteCommand对象。
- 该Invoker通过调用Command对象的Execute操作来提交一个请求。若该命令是可撤销的,ConcreteCommand就在执行Execute操作之前存储当前状态以用于取消该命令。
- ConcreteCommand对象调用它的Receiver的一些操作以执行该请求。
3. 实例分析
考虑我们之前提到的用户工具箱中菜单的例子:用Command对象可以很容易的实现菜单(Menu),每一菜单中的选项都是一个菜单项(MenuItem)类的实例。我们应该为每一个菜单项配置一个具体的Command子类的实例。当用户选择一个菜单项时,该MenuItem对象调用它的Command对象的Execute方法,而Execute执行相应操作。MenuItem对象并知道它们使用的是Command的哪一个子类。Command子类里存放着请求的接收者,而Execute操作将调用该接收者的一个或多个操作。
在我们的例子中有一个CApplication类用于管理打开的文档和菜单项(仅仅只是为了管理菜单项和打开的文档,与命令模式没有什么特殊关系)。并且提供了一个CMacroCommand类管理一个子命令序列,它提供了增加和删除子命令的操作。
/*********************************************************************************
*Copyright(C),Your Company
*FileName: Command.h
*Author: Huangjh
*Version:
*Date: 2017-11-25
*Description: 相当于结构图中的Command角色——声明执行操作的接口
并定义一个与Component接口一致的接口
*Others:
**********************************************************************************/
#ifndef _COMMAND_H
#define _COMMAND_H
class CCommand
{
public:
virtual ~CCommand() { }
virtual void Execute() = 0;
protected:
CCommand() { }
};
#endif //#ifndef _COMMAND_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: OpenCommand.h
*Author: Huangjh
*Version:
*Date: 2017-11-25
*Description: 相当于结构图中的ConcreteCommand角色——将一个接收者绑定于open命令
并定义一个与Component接口一致的接口
*Others:
**********************************************************************************/
#ifndef _OPEN_COMMAND_H
#define _OPEN_COMMAND_H
#include "Command.h"
#include "Document.h"
#include "Application.h"
class COpenCommand : public CCommand
{
public:
COpenCommand(CApplication *pApplication, CDocument *pDocument)
:m_pApplication(pApplication)
,m_pDocument(pDocument)
{
}
virtual void Execute()
{
m_pApplication->addDocument(m_pDocument);
m_pDocument->open();
}
private:
CApplication *m_pApplication;
CDocument *m_pDocument;
};
#endif //#ifndef _OPEN_COMMAND_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: PasteCommand.h
*Author: Huangjh
*Version:
*Date: 2017-11-25
*Description: 相当于结构图中的ConcreteCommand角色——将一个接收者绑定于粘贴操作
*Others:
**********************************************************************************/
#ifndef _PASTE_COMMAND_H
#define _PASTE_COMMAND_H
#include "Command.h"
#include "Document.h"
class CPasteCommand : public CCommand
{
public:
CPasteCommand(CDocument *pDocument)
:m_pDocument(pDocument)
{
}
virtual void Execute()
{
if (m_pDocument != NULL)
m_pDocument->paste();
}
private:
CDocument *m_pDocument;
};
#endif //#ifndef _PASTE_COMMAND_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: MacroCommand.h
*Author: Huangjh
*Version:
*Date: 2017-11-25
*Description: 相当于结构图中的ConcreteCommand角色——提供管理子命令序列
*Others:
**********************************************************************************/
#ifndef _MACRO_COMMAND_H
#define _MACRO_COMMAND_H
#include <set>
#include "Command.h"
#include "Document.h"
class CMacroCommand : public CCommand
{
public:
CMacroCommand() { }
virtual ~CMacroCommand() { }
virtual void Add(CCommand *pCommand)
{
m_setMacroCommand.insert(pCommand);
}
virtual void Remove(CCommand *pCommand)
{
std::set<CCommand *>::iterator iter;
iter = m_setMacroCommand.find(pCommand);
if (iter != m_setMacroCommand.end())
m_setMacroCommand.erase(iter);
}
virtual void Execute()
{
std::set<CCommand *>::iterator iter;
for (iter = m_setMacroCommand.begin(); iter != m_setMacroCommand.end(); ++iter)
{
(*iter)->Execute();
}
}
private:
std::set<CCommand *> m_setMacroCommand;
};
#endif //#ifndef _MACRO_COMMAND_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: Document.h
*Author: Huangjh
*Version:
*Date: 2017-11-25
*Description: 相当于结构图中的Receiver角色——文档操作类
*Others:
**********************************************************************************/
#ifndef _DOCUMENT_H
#define _DOCUMENT_H
#include <iostream>
#include <string>
class CDocument
{
public:
CDocument(const std::string &strName)
:m_strFileName(strName)
{
}
void open()
{
std::cout << "创建一个Document : " << m_strFileName << std::endl;
}
void close()
{
std::cout << "关闭一个Document : " << m_strFileName << std::endl;
}
void paste()
{
std::cout << "向Document : " << m_strFileName << "粘贴正文" << std::endl;
}
std::string getDocumentName()
{
return m_strFileName;
}
private:
std::string m_strFileName;
};
#endif //#ifndef _DOCUMENT_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: MenuItem.h
*Author: Huangjh
*Version:
*Date: 2017-11-25
*Description: 相当于结构图中的Invoke角色——某个菜单项
*Others:
**********************************************************************************/
#ifndef _MENU_ITEM_H
#define _MENU_ITEM_H
#include <cstddef>
#include <string>
#include "Command.h"
class CMenuItem
{
public:
CMenuItem(const std::string &strName)
:m_strMenuItemName(strName)
,m_pCommand(NULL)
{
}
void SetCommand(CCommand *pCommand)
{
this->m_pCommand = pCommand;
}
void ExcuteCommand()
{
m_pCommand->Execute();
}
private:
std::string m_strMenuItemName;
CCommand *m_pCommand;
};
#endif //#ifndef _MENU_ITEM_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: Applicationt.h
*Author: Huangjh
*Version:
*Date: 2017-11-25
*Description: 相当于结构图中的Client角色——简单的用于跟踪用户已打开的Document对象和绑定的菜单项
*Others:
**********************************************************************************/
#ifndef _APPLICATION_H
#define _APPLICATION_H
class CDocument;
class CMenuItem;
#include <set>
class CApplication
{
public:
void addDocument(CDocument *pDocument)
{
SetDocument::iterator iter = m_setDocument.find(pDocument);
if (iter != m_setDocument.end())
m_setDocument.insert(pDocument);
}
void delDocument(CDocument *pDocument)
{
SetDocument::iterator iter = m_setDocument.find(pDocument);
if (iter != m_setDocument.end())
m_setDocument.erase(iter);
}
void addMenuItem(CMenuItem *pMenuItem)
{
SetMenuItem::iterator iter = m_setMenu.find(pMenuItem);
if (iter != m_setMenu.end())
m_setMenu.insert(pMenuItem);
}
void delMenuItem(CMenuItem *pMenuItem)
{
SetMenuItem::iterator iter = m_setMenu.find(pMenuItem);
if (iter != m_setMenu.end())
m_setMenu.erase(iter);
}
private:
typedef std::set<CDocument *> SetDocument;
typedef std::set<CMenuItem *> SetMenuItem;
SetDocument m_setDocument;
SetMenuItem m_setMenu;
};
#endif //#ifndef _APPLICATION_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: main.cpp
*Author: Huangjh
*Version:
*Date: 2017-11-25
*Description: 命令模式的测试用例
*Others:
**********************************************************************************/
#include "OpenCommand.h"
#include "PasteCommand.h"
#include "MacroCommand.h"
#include "Application.h"
#include "Document.h"
#include "MenuItem.h"
int main(void)
{
CApplication *pApplication = new CApplication();
CDocument *pDocument = new CDocument("命令模式文档");
CMenuItem *pMenuItem = new CMenuItem("文档菜单");
pApplication->addMenuItem(pMenuItem);
COpenCommand *pOpenCommand = new COpenCommand(pApplication, pDocument);
CPasteCommand *pPasteCommand = new CPasteCommand(pDocument);
CMacroCommand *pMacroCommand = new CMacroCommand();
pMacroCommand->Add(pOpenCommand);
pMacroCommand->Add(pPasteCommand);
pMenuItem->SetCommand(pMacroCommand);
pMenuItem->ExcuteCommand();
return 0;
}
运行结果如下所示:创建一个Document : 命令模式文档
向Document : 命令模式文档粘贴正文
4. 优缺点
4.1 优点
- Command模式将调用操作的对象和知道如何实现该操作的对象解耦。
- Command是头等对象。它们可像其他的对象一样被操纵和扩展。
- 你可以将多个命令装配成一个复合命令。例如是例子中的MacroCommand类。一般来说,复合命令是Composite模式的一个实例。
- 增加新的Command很容易,因为这无需改变已有的类。
5. 适用性
当你有如下需求时,可使用Command模式:
- 像上面讨论的MenuItem对象那样,抽象出待执行的动作以参数化某对象。你可用过程语言中的回调函数表达这种参数化机制。Command模式是回调机制的一个面向对象的替代品。
- 在不同的时刻指定、排列和执行请求。一个Command对象可以有一个与初始化请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可以将负责该请求的命令对象传送给另一个不同的进程并在那儿实现该请求。
- 支持取消操作。Command的Excute操作可在实施操作前将状态存储起来,在取消操作时取消上一次Execute调用的效果。
- 支持修改日志,这样当系统崩溃是,这些修改可以被重做一遍。在Command接口中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用Execute操作重新执行它们。
- 用构建在原语操作上的高层操作构造一个系统。这样一种结构在支持事务的信息系统是很常见的。一个事务封装了对数据的一组变动。Command模式提供了对事务进行建模的方法。Command有一个公共的接口,使得你可以用一种方式调用所有的事务。同时使得该模式也易于添加新事务以扩展系统。
6. 参考资料
- 《大话设计模式》
- 《设计模式——可复用的面向对象软件的基础》