五、命令模式定义
命令模式(Command Pattern )是对命令的封装,每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式解耦了请求方和接收方,请求方只需请求执行命令,不用关心命令是怎样被接收,怎样被操作以及是否被执行…等。
命令模式属于行为型模式。
原 文 : Encapsulate a request as an object, there by letting you parameterize clients with different requests, queue or log requests,and support undoable operations.
解释:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能
在软件系统中,行为请求者与行为实现者通常是一种紧耦合关系,因为这样的实现简单明了。
但紧耦合关系缺乏扩展性,在某些场合中,当需要为行为进行记录,撤销或重做等处理时,只
能修改源码。而命令模式通过为请求与实现间引入一个抽象命令接口,解耦了请求与实现,并
且中间件是抽象的,它可以有不同的子类实现,因此其具备扩展性。所以,命令模式的本质是
解耦命令请求与处理。
六、命令模式的应用场景
当系统的某项操作具备命令语义时,且命令实现不稳定(变化),那么可以通过命令模式解耦请求与实现,利用抽象命令接口使请求方代码架构稳定,封装接收方具体命令实现细节。接收方与抽象命令接口呈现弱耦合(内部方法无需一致),具备良好的扩展性。
命令模式适用于
以下应用场景:
1、现实语义中具备"命令"的操作(如命令菜单,shell命令…);
2、请求调用者和请求的接收者需要解耦,使得调用者和接收者不直接交互;
3、需要抽象出等待执行的行为,比如撤销(Undo)操作和恢复(Redo)等操作;
4、需要支持命令宏(即命令组合操作)。
从 UML类图中,我们可以看到,命令模式 主要包含四种角色:
接收者角色(Receiver):该类负责具体实施或执行一个请求;
命令角色(Command ):定义需要执行的所有命令行为;
具体命令角色(Concrete Command )该类内部维护一个接收者(Receiver ),在其execute()
方法中调用Receiver的相关方法;
请求者角色(Invoker):接收客户端的命令,并执行命令。
从命令模式的UML类图中,其实可以很清晰地看出:Command的出现就是作为Receiver 和 Invoker的中间件,解耦了彼此。而之所以引入Command中间件,我觉得是以下两方面原因 :
解耦请求与实现:即解耦了 Invoker和 Receiver , 因为在UML类图中,Invoker是一个具
体的实现,等待接收客户端传入命令(即 Invoker与客户端耦合),Invoker处于业务逻辑区域, 应当是一个稳定的结构。而 Receiver是属于业务功能模块 是经常变动的 如果没有Command z 则 Invoker紧耦合Receiver , 一个稳定的结构依赖了一个不稳定的结构,就会导致整个结构都不稳定了。这也就是Command引入的原因:不仅仅是解耦请求与实现,同时稳定( Invoker)
依赖稳 定 (Command ),结构还是稳定的。
扩展性增强:扩展性体现在两个方面:
1、 Receiver属于底层细节,可以通过更换不同的Receiver达到不同的细节实现;
2、 Command接口本身就是抽象的,本身就具备扩展性;而且由于命令对象本身就具备抽 象 ,如果结合装饰器模式,功能扩展简直如鱼得水。
注:在一个系统中,不同的命令对应不同的请求,也就是说无法把请求抽象化,因此命令模式中的Receiver是具体实 现;但是如果在某一个模块中,可以对Receiver进行抽象,其实这就变相使用到了桥接模式(Command类具备两个变 化的维度:Command和 Receiver), 这样子的扩展性会更加优秀。
举个生活中的例子,相信80后的小伙伴应该都经历过普及黑白电视机的那个年代。黑白电
视机要换台那简直不容易,需要人跑上前去用力掰动电视机上那个切换频道的旋钮,一 顿 〃啪
啪啪〃折腾下来才能完成一次换台。如今时代好了,我们只需躺沙发上按一下遥控器就完成了
换台。这就是用到了命令模式,将换台命令和换台处理进行了分离。
另外,就是餐厅的点菜单,一般是后厨先把所有的原材料组合配置好了,客户用餐前只需要 点菜即可,将需求和处理进行了解耦。
19.3.命令模式在业务场景中的应用
假如我们自己开发一个播放器,播放器有播放功能、有拖动进度条功能、停止播放功能、暂
停功能,我们自己去操作播放器的时候并不是直接调用播放器的方法,而是通过一个控制条去传达指令给播放器内核,那么具体传达什么指令,会被封装为一个一个的按钮。那么每个按钮
的就相当于是对一条命令的封装。用控制条实现了用户发送指令与播放器内核接收指令的解耦。
下面来看代码,首先创建播放器内核GPlayer类:
public class GPlayer { public void play(){ System.out.println("正常播放"); } public void speed(){ System.out.println("拖动进度条"); } public void stop(){ System.out.println("停止播放"); } public void pause(){ System.out.println("暂停播放"); } }