行为型模式--Command模式(命令)对象行为型模式
一. 意图
将一个请求封装为一个对象, 从而使你可用不同的请求对客户进行参数化; 对请求排队或记录请求日志, 以及支持可撤消的操作.(封装成对象, 从而可以利用面向对象中继承, 多态, 重载等特性, 从而使得你的设计更灵活, 独立, 解耦, 内聚)
二. 适用性
这一模式的关键是一个抽象的Command类, 它定义了一个执行操作的接口. 其最简单的形式是一个抽象的Execute操作. 具体的Command子类将接收者作为其一个实例变量, 并实现Execute操作, 指定接收者采取的动作. 而接收者有执行该请求所需的具体信息.
当你有如下需求时, 可使用Command模式:
1. 像MenuItem对象那样, 抽象出待执行的动作以参数化某对象. 你可用过程语言中的回调(callback)函数表达这种参数化机制. 所谓回调函数是指函数先在某处
注册, 而它将在稍后某个需要的时候被调用. Command模式是回调机制的一个面向对象的替代品.
2. 在不同的时刻指定、排列和执行请求. 一个Command对象可以有一个与初始请求无关的生存期. 如果一个请求的接收者可用一种与地址空间无关的方式表达, 那么就可将负责该请求的命令对象传送给另一个不同的进程并在那儿实现该请求.
3. 支持取消操作. Command的Excute操作可在实施操作前将状态存储起来, 在取消操作时这个状态用来消除该操作的影响. Command接口必须添加一个Unexecute操作, 该操作取消上一次Execute调用的效果. 执行的命令被存储在一个历史列表中. 可通过向后和向前遍历这一列表并分别调用Unexecute和Execute来实现重数不限的“取消”和“重做”.
4. 支持修改日志, 这样当系统崩溃时, 这些修改可以被重做一遍. 在Command接口中添加装载操作和存储操作, 可以用来保持变动的一个一致的修改日志. 从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用Execute操作重新执行它们.
5. 用构建在原语操作上的高层操作构造一个系统. 这样一种结构在支持事务(transaction)的信息系统中很常见. 一个事务封装了对数据的一组变动. Command模式提供了对事务进行建模的方法. Command有一个公共的接口, 使得你可以用同一种方式调用所有的事务. 同时使用该模式也易于添加新事务以扩展系统.
三. 模式结构
图1
Invoker与Receiver并不知道对方, 解耦了
四. 角色说明
Command
—声明执行操作的接口.
ConcreteCommand
—将一个接收者对象绑定于一个动作.
—调用接收者相应的操作, 以实现Execute.
Client
—创建一个具体命令对象并设定它的接收者。
Invoker
—要求该命令执行这个请求.
Receiver
—知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者.
五. 执行
图2
Client创建一个ConcreteCommand对象并指定它的Receiver对象. 某Invoker对象存储该ConcreteCommand对象. 该Invoker通过调用Command对象的Execute操作来提交一个请求. 若该命令是可撤消的, ConcreteCommand就在执行Excute操作之前存储当前状态以用于取消该命令.
ConcreteCommand对象对调用它的Receiver的一些操作以执行该请求。
// aClient中
// 1. 创建关联Receiver与Command的关联
pReceiver = new Receiver();
pCommand = new Command(pReceiver);
// 2. 创建Invoker于Command的关联.
// 例如把pCommand传递给Invoker, 反正Invoker知道对象pCommand
// 3. Invoker触发(调用)Command执行
pCommand->Execute();
六. 说明
1. Command模式将调用操作的对象与知道如何实现该操作的对象解耦(将调用者和接收者解耦)
2. Command是头等的对象. 它们可像其他的对象一样被操纵和扩展.
3. 你可将多个命令装配成一个复合命令. 一般说来, 复合命令是Composite模式的一个实例.
4. 增加新的Command很容易, 因为这无需改变已有的类.
5. 一个命令对象应达到何种智能程度 命令对象的能力可大可小. 可以只是简单的调用aReceiver->Action(), 也可以自己独立实现aReceiver的Action()(个人认为只要达到调用者和接收者解耦就好, Invoker一定通过调用Command::Execute()来完成操作即可).
6. 支持后退和前进(撤销和向前). 为达到这个目的, ConcreteCommand类可能需要存储额外的状态信息. (个人认为支持撤销操作这种功能, 可能是比较适合使用这种模式来实现, 但是关键还是看你怎么保存和记录状态信息, 使得能支持撤销操作.)
六. 我的理解:
1. Command模式目的是将调用者和接收者解耦. 自己开始的理解是Receiver中包含一个Command对象, 这样子很明显Invoker是通过Receiver来调用Command::Execute了. Receiver与Receiver是不能解耦的.
2. 对于撤销功能, 当执行一条命令之前, 你需要保存当前的状态, 并放入历史列表.
a. 可以这样子实现: 定义Command有记录执行之前的状态的能力. 有一个撤销函数用于恢复状态(UnDo()).
b. 在Command执行之前, 使用Prototype模式, 保存该Command到操作历史列表. 执行该Command
c. 如果要撤销, 就从历史列表取出最后入队的Command对象, 调用UnDo()函数.
要注意的还是怎么保存这些状态, 怎么恢复这些状态, 这些状态对全局的状态有什么影响.
3. 菜单栏的菜单项使用这种模式, 你点击菜单项时, 执行的过程就是Command::Execute() ; Command::Execute()里面调用Receiver::Action(), 菜单项并不需要知道Receiver的具体细节. 在程序的某个地方, 有代码把 Command与Receiver绑定在一起, 也有代码把菜单项与Command绑定在一起. (菜单项与Receiver直接隔着Command, 从而解耦).
4. 能这样子吗?
图3
具体的Receiver与具体的Command解耦了. 这样子也可以, 如果满足你的需求的话, 起码就是Receiver有公共的接口.
而在图1中子Command与对应的Receiver是必须关联的, 这也是正确的. 反正就是看你的需求而定. 而图3中则可以在运行时来决定Receiver, 而图1是一个子Command则固定的对应了其Receiver. 通常如果各个Receiver之间没有能提取出公共接口, 子Command与Receiver一一对应也是很正常的.
5. 从点4可以看到Command模式的关键Command与Receiver的关联和Invoker与Command的内聚. (Command隔开了Invoker与Receiver)
七. 相关模式:
Composite模式可被用来实现宏命令. Memento模式可用来保持某个状态, 命令用这一状态来取消它的效果. 在被放入历史表列前必须被拷贝的命令起到一种原型(Prototype模式)的作用.