一、认识命令模式(最简单的命令模式)
1、实现命令接口,让所有的命令对象实现相同的包含一个方法的接口。图01.jpg
2、现在,假设想实现一个打开电灯的命令,Light类有两个方法:on()和off()。将它实现成一个命令。图02.jpg
3、使用命令对象。假设我们有一个遥控器,它只有一个按钮,可一个控制一个装置。图03.jpg
4、遥控器使用的简单测试。图04.jpg
从上面的一系列的图可以看出:封装是其核心。
我们把封装带到了一个全新的境界:把方法调用(Method invocation)封装起来。通过封装方法调用,我们可以把运算块包装成形。所以调用此运算的对象不需要关心事情是如何进行的,只要知道如何使用包装成形的方法来完成它就可以。通过封装方法调用,也可以做一些很聪明的事情,例如记入日志,或者重复使用这些封装来实现撤销(undo)。
二、定义命令模式
命令模式 将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。图05.jpg
现在,仔细看这个定义。我们知道一个命令对象通过在特定接受者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接受者包进对象中。这个对象只暴露出一个execute()方法,当此方法被调用的时候,接受者就会进行这些动作。从外面来看,其他对象不知道究竟哪个接受者进行了哪些动作,只知道如果调用execute()方法,请求的目的就能达到。
定义命令模式的类图:图06.jpg
三、命令模式的更多用途
1、队列请求
命令可以将运算块打包(一个接受者和一组动作),然后将它传来传去,就像是一般的对象一样。现在,即使在命令对象被创建许久之后,运算依然可以被调用。事实上,它甚至可以在不同的线程中被调用。我们可以利用这样的特性衍生一些应用,例如:日程安排、线程池和工作队列等。
想象有一个工作队列:你在某一端添加命令,然后另一端则是线程。线程进行下面的动作:从队列中取出一个命令,调用它的execute()方法。等待这个调用完成,然后将此命令对象丢弃,再取出下个命令……。请注意,工作队列类和进行计算的对象之间完全是解耦的。图07.jpg
2、日志请求
某些应用需要我们将所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前的状态。通过新增两个方法(store()、load()),命令模式就能够支持这一点。图08.jpg。在java在中,我们可以利用对象的序列化实现这些方法,但是一般认为序列化最好还是只用在对象的持久化上。
要怎么做呢?当我们执行命令的时候,将历史记录存储在磁盘中。一旦系统死机,我们就可以将命令对象重新加载,并成批地依次调用这些对象的execute()方法。
有这样的场景:有许多调用大型数据结构的动作的应用无法在每次改变发生时被快速地存储。通过使用记录日志,我们可以将上次检查点(checkpoint)之后的所有操作记录下来,如果系统出了问题,从检查点开始应用这些操作。比方说,对于电子表格应用,我们可以想要实现的错误恢复方式是将电子表格的操作记录在日志中,而不是每次电子表格一有变化就记录整个电子表格。对更高级的应用而言,这些技巧可以被扩展应用到事务处理中,也就是说,一整群操作必须全部进行完成,或者没有进行任何操作。图09.jpg
四、回顾要点
1、命令模式将发出请求的对象和执行请求的对象解耦
2、在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接受者和一个或一组动作。
3、调用者通过调用命令对象的exe()发出支持请求,这会使得接受者的动作被调用。
4、调用者可以接受命令当做参数,甚至在运行时动态地进行。
5、命令可以撤销,做法是实现一个undo()方法来回到execute()被执行前的状态。
6、宏命令是命令的一种简单的延伸,允许调用多个命令。宏方法也可以支持撤销。
7、实际操作时,很常见使用“聪明”命令对象,也就是直接实现了请求,而不是将工作委托给接受者。
8、命令也可以用来实现日志和事务系统。