在这篇博客里,我们一起来看一下命令模式。
为了理解命令模式,我们可以从生活中常见的场景入手,
例如客户到餐厅点餐的场景。
如上图所示,客户进入餐厅后的最终目的是获取食物。
于是,客户会创建一个订单,并交给服务员。
服务员取得订单后,实际上不需要关心订单上写了什么,
也不需要知道将由谁来准备餐点,
只需要将订单放置到订单窗口,然后喊一声“订单来了”即可。
厨师收到订单通知后,就会准备对应的食物,
并最终递交给客户。
上述场景的交互方式,实际上就是命令模式的雏形。
命令模式可将“动作的调用者”和“动作的执行者”解耦。
例如上图中,服务员就是“动作的调用者”;而厨师就是“动作的执行者”。
服务员不需要和厨师直接打交道,而是递交对应的订单对象;
厨师收到订单对象后,就会执行具体的操作。
通过这种方式,服务员不需要知道厨师准备食物的相关细节,
仅需要明白订单对象的外部接口,例如:放到订单窗口这个动作。
在理解上述生活场景后,我们就可以来看看命令模式的结构了:
如上图所示,Client负责创建一个ConcreteCommand,并设定其接收者。
在之前的例子中,这部分内容相当于:客户创建了一个订单,且订单的执行者指定是厨师(隐含条件)。
Invoker是命令的调用者,负责持有命令对象,
并在某个时刻调用命令对象的execute方法,实施用户的请求。
这个过程就像之前例子中服务员的工作。
服务员负责持有用户的订单,然后在合适的时候,
将订单放到订单窗口,触发厨师进行实际的工作。
Command为所有的命令声明了一个接口。
调用命令的execute方法,就可以让接收者进行相关的动作。
这个也比较好理解。
有的用户可能需要烤肉,有的用户可能需要蛋糕,
但对于服务员而言,这都只不过是订单的细节而已,
服务员只需要关注订单本身,即Command接口。
ConcreteCommand定义了动作和接收者之间的绑定关系。
调用者只要调用execute就可以发出请求,然后由ConcreteCommand调用接收者的一个或多个动作。
ConcreteCommand就对应之前例子中具体的订单了,
客户点了不同的菜时,其实就隐式指定了对应菜系的厨师。
当在订单窗口看到对应的订单后,就会通知具体的厨师工作。
Receiver就是动作的实际执行者了,负责完成用户的请求。
这个就是前例中的厨师了。
从上图可以看出,引入Command对象后,
解除了Invoker和Receiver之间的耦合。
了解命令模式的结构后,我们再来看看命令模式在Head First中的定义。
命令模式将“请求”封装成对象,以便使用不同的请求、队列
或者日志来参数化其他对象。命令模式也支持可撤销的操作。
咋一看这个定义,绝对是一脸懵逼的,个人觉得它的重点其实就是:
将“请求”封装成对象。
一个命令对象就是通过在特定的接收者上绑定一组动作来实现的。
这个对象只需要暴露出一个execute方法,当此方法被调用时,
接收者就会执行绑定的动作。
当然在实际使用时,可以不为命令对象指定具体的接收者,
而是直接在execute中实现许多逻辑。
不过相比较而言,命令对象包含接收者时,
可以将接收者作为参数传递给命令对象,
达到运行时调整的效果,这样的设计可能更有弹性。
命令模式在实际代码得到了广泛的使用。
比较常见的业务结构类似于:
许多实现Command接口的命令对象被加入到Command Queue中;
dispatcher不需要知道每个Command对象具体在干什么,
只需要取出Command对象,然后调用对应的execute方法。
一般情况下,为了提高处理的效率,还会引入多个线程并发处理。
这种设计结构,在之前分析Volley框架时也有提及。
Volley实际上就是将实现Request接口的对象,加入到RequestQueue中。
NetworkDispatcher从RequestQueue中取出Request,
然后调用Request中对应的接口进行实际的处理。