RSS

Command pattern 应用特点

19 May

命令模式是一个很有活力的设计模式,它的几项特点具有很好的利用价值,我们可以通过演变该模式为许多问题找到解决方案。

先教科书式地摆出命令模式的UML图,并有请模式中各角色登场。看过之后请一定记住,不能教科书式地背诵它们或使用它们!不能拘泥于教科书式的命令模式表述,发挥其特点和思路才是正途。设计模式提炼自问题又用来交流并重复解决问题,所以为具体的问题去理解并打造它吧。

1、批量执行命令

通过实现Command接口,各实现类实例可聚合在一起并被Invoker遍历,依次执行各命令的Execute方法,达到批量执行命令的目的。如果有需要的话,每个命令执行的返回结果都可在Invoker处收集到。

这里举参数检查的例子。如筛选面试者信息时,需要一一检查各关键参数是否满足要求:email要验证格式,年龄段要符合限制,各项评分达到相应要求等等。

  • 普通写法什么样儿?若干含有“ValidatingXXX”方法的if语句形成丑陋的排比句,凡是返回值出现false,一律终止之后的检查并取消后续的业务逻辑执行。
  • 命令模式带给你清净。对着图,Client把典型验证逻辑包含在若干ConcreteCommand对象当中,形成命令集合交给Invoker,再将面试者信息对象填入,由Invoker执行各命令的Execute方法分别去验证输入对象的参数,Invoker可随时观察到返回false的命令执行结果并终止后续逻辑。Receiver在上述过程中可以不必出现,因为还没有出现需要让其他某各对象特地做点什么的时候。

2、命令对象的存在感

狭义地讲,命令就是希望完成的意图。调用方法是达到意图的过程,可惜这个过程从期望执行到开始执行是瞬间启动的,方法执行完毕命令的效果也就结束了,烟消云散。当我们将命令寄居在对象上的时候,这个意图的生命周期可见了。

  • 从发布命令到命令被执行,对象可以等待,于是时间上的耦合被解开了。我们可以为创建的命令对象设置延时,让需要深夜执行的程序在白天就发布给命令执行引擎。
  • 从参数的接收、验证到命令执行,工作可以分派,所以对象实体上的耦合被解开了。我们可以为ConcreteCommand类对象指定多个Receiver,让它们分别完成参数的验证和意图的执行。

毫无疑问,面向对象的世界里,方法也可被视为对象。Function Object被多种编程语言支持着且不说,就连我所学的第一门编程语言——C语言,也能以函数指针的方式将意图指定并保留下来,之后执行。如果情况适合的话,也可以试用它们完成命令模式的思想,不过一个命令实现类还是在提供辅助方法以及实现统一接口上有更大的操作空间。

3、命令的执行对发布者透明

命令模式最基本的模型,只有commander类和若干command实现类,对象之间是1对N的关系。其中,commander对象负责在各种情形下发出各种命令,即同步或异步地安排command对象执行Execute方法;command对象在得到执行许可后,全权接管具体执行过程。

好处是显而易见的。就像掌控全局的指挥官,了解了各种命令能达到的意图,按需发出命令就是了,不需要知道是谁又是如何让这条命令落到实处。是不是想到了手握遥控器那一刻的自己呢?

命令模式对“事件-响应”模型有良好的支持。我们常使用dispatcher将响应函数与事件特征相关联,如MVC中由router将Request映射到Controller某方法上,将对响应函数的调用换作命令的发出,就换到了命令模式的思维上。综合上一节对命令对象的阐述,

  • 我们可以在命令发出时立即调用其Execute方法,这与直接调用响应函数等效;我们也可利用命令对象的长命,以异步方式推迟执行命令的方法;
  • 我们不需要在dispatcher上就知道要调用哪个对象的哪种行为作为响应,只需要知道发出那类命令。
  • 我们可以利用命令对象对外暴露接口的一致性,借用命令对象作为Wrapper包装个性不一的响应函数,dispatcher更轻量和整洁。

4、命令的追踪和undo

命令模式以对象为粒度管理命令,命令对象创建后并未立即执行,命令执行完毕后对象却仍未销毁。我们可将未执行的命令入队变成序列,将已执行的命令保存为历史,这样来追踪命令。

命令模式中命令被实现类所定义,为其关联除Execute接口方法之外的方法很方便,其中很有意义的一个思路是为可逆命令添加undo方法。前面说到可追踪已执行的命令,那么对一个全部实现了undo方法的命令序列,将每条命令执行后入栈,即可随时通过出栈并执行undo的方式实现回滚。(如果没有undo方法,那么实现回滚就意味着要从一个起点开始将之后到记录中某点之前的所有命令重新执行。)

用两个应用实例来说明它的优势。

  • 事务。如果将事务中的执行步骤切分为若干连续的命令,那么事务的开始就是这一命令序列的执行开始,事务成功完成的标志是最后一个命令成功执行,而事务失败后回滚则是从失败点开始将前面的命令按序列倒序执行undo方法直至开始。
  • 增量型操作的redo与undo。如使用Photoshop对图片做处理,每步操作都是一个Command,由对应的工具作为其Receiver在上一操作完成后的图像上做出修改,并如实记录该Command及相关参数在历史列表中。我们可以使用undo回滚当前一步的修改,或再使用redo应用它;当点选历史列表中的某项时,该项之后的历史操作被依次undo,即回滚到指定位置。

5、命令队列与Active Object模式

命令模式满足Active Object模式的六要素:

  1. 供外部访问的代理,能构造请求并入队——Client;
  2. 所有Active Object实现了统一的接口——Command::Execute;
  3. 待处理的请求序列——CommandQueue;
  4. 维护请求序列并安排处理队首请求的引擎——ActiveObjectEngine;
  5. Active Object中处理请求的实现者或实现方法——Receiver;
  6. 一种将结果向外传达的结果返回或函数回调方式——Receiver;

实现的核心思想是:在执行中的队首命令有能力将新命令加至队列尾;等待事件或资源(如时间片)的方式是以持续查询的方式不断将仍不满足执行条件的队首命令添加至队尾。还有,CPU时钟与实时时钟是不同步的,利用延时检查作为调度条件会出现一定不确定性,这种性质正是多线程系统的特点。

Active Object模式是实现多线程调度的一种典型方式。利用延时等待线程的时间片,在时间片轮转到时开始对应线程的临界资源检查,满足运行条件时唤醒线程。延时长短的设置可反映线程的非抢占式优先级。

Active Object模式可用来以单线程模拟多线程系统。这里对模拟的线程是有要求的,需要RTC(run-to-completion)线程。这里利用到了命令一经执行就一定得完成的性质,可以看到,多个命令其实是有同步完成执行的关系。这样只需单一的工作者线程,在不同时间段执行不同的任务,避免了为各个任务分别分配各自的运行时堆栈,这在内存受限系统中是一个不错的节约。

 

About Wu Shaobo

@ThoughtWorks

One Response to Command pattern 应用特点

  1. Tuo

    2011-06-08 at 09:50

    收录了~~