任何模式的出现,都是为了解决一些特定的场景的耦合问题,以达到对修改封闭,对扩展开放的效果。命令模式也不例外:
命令模式是为了解决命令的请求者和命令的实现者之间的耦合关系。
解决了这种耦合的好处我认为主要有两点:
1.更方便的对命令进行扩展(注意:这不是主要的优势,后面会提到)
2.对多个命令的统一控制(这种控制包括但不限于:队列、撤销/恢复、记录日志等等)
模式解析:
经典的命令模式包括4个角色:
Command:定义命令的统一接口
CommondImpl:Command接口的实现者,用来执行具体的命令,某些情况下可以直接用来充当Receiver。
Receiver:命令的实际执行者
Invoker:命令的请求者,是命令模式中最重要的角色。这个角色用来对各个命令进行控制。
下面对上面四个角色的经典实现用代码来进行说明,这也是大部分文章对命令模式的运用方式。
经典代码实现:
/* * 命令接口,声明执行的操作 */public interface Commond { /* * 执行命令对应的操作 */ public void excute();}/** * @ClassName:CommondImpl * @Description:命令接口的具体实现类 */public class CommondImpl implements Commond{ /* * 持有相应的接收者对象 */ Receiver receiver; //示意属性,命令对象可以有自己的属性 public int state; //构造方法,传入接收者对象 public CommondImpl(Receiver r){ receiver=r; } //调用接收者的对于功能方法,由接收者执行具体的功能 @Override public void excute() { //可以做一些其他处理 receiver.action(); }}/** * @ClassName:Receiver * @Description:接收者类 */public class Receiver { /** * @Title:action * @Description:示意操作,真正执行命令相应的操作 */ public void action(){ //功能代码 }}/** * @ClassName:Invoker * @Description:在应用程序中通过该类来触发执行命令 */public class Invoker { private Commond commond; public void setCommond(Commond commond) { this.commond = commond; } //调用命令对象执行方法 public void runCommond(){ commond.excute(); }}//客户端调用public class Client { public static void main(String[] args) { //组装命令对象与接收者对象,将命令对象与接收者对象建立起联系 Receiver r=new Receiver(); Commond c=new CommondImpl(r); //创建Invoker对象,并设置命令对象,然后运行命令 Invoker invoker=new Invoker(); invoker.setCommond(c); invoker.runCommond(); }}
经典实现解析:
不知道大家看过上面的代码之后是什么感觉,反正我看过上面的代码之后第一反应确实是越看越糊涂了,主要觉得有几点疑问:
1. 执行命令可以,但是为什么要用命令封装起来,这不是有点脱裤子放屁的感觉么?我完全可以这样写:
public class Client { public Client() { Receiver receiver = new Receiver(); receiver.action(); }}
这样不是更加简单明了?两个类搞定。
2. 通过继承Command之后,增加命令怎么增加?比如增加一个命令,要改动3个地方:增加一个Command实现,修改Receiver类,修改Client。这好像没有对修改关闭啊?
3. CommondImpl与Receiver类完全耦合了啊,要是有CommondImpl以及另一个不同的CommondImpl要执行的命令在不同的Receiver中怎么办?
那来看看到底疑问在哪里:
1. 确实可以两个类来搞定。但我们要牢记命令模式的初衷:对命令请求者(Invoker)和命令实现者(Receiver)的解耦,方便对命令进行各种控制。打个比方,现在我们要对CommondImpl以及其他一系列命令进行日志记录,并且两个命令之间的操作间隔不能大于1秒。这种情况下要直接用两个类就会有大量的业务逻辑要在客户端进行处理,当命令增加,对每个命令的控制增加时,就会在Client里面产生大量的变化点,这样耦合就出来了,但是采用命令模式之后,对着一系列的命令我们都可以进行控制,这就是对变化点的封装,
2. 增加命令:采用命令模式的时候,我感觉最大的耦合点变化到了Receiver和CommondImpl之间,当然我们可以对Receiver进行抽象,采用接口或者抽象类来封装这个变化,但实际情况中我们会遇到多个命令来至于不同的Receiver,比如A命令来至于ReceiverA,B命令来至于ReceiverB,这种情况下我们怎么应对命令的新增?对这种情况我的理解是命令模式并不能也不需要解决这个问题,因为命令模式的操作单元已经细化到了每一个具体的功能上面,当增加一个具体功能的时候是没有很好的办法对功能实现类进行修改关闭的(当然你可以把每个功能方法放到一个类中,但确实没必要,这个粒度已经很小了),实际上也没有必要的。
适用场景:
1. 命令的发送者和命令执行者有不同的生命周期。命令发送了并不是立即执行。
2. 命令需要进行各种管理逻辑。
3. 需要支持撤消\重做操作(这种状况的代码大家可以上网搜索下,有很多,这里不进行详细解读)。
结论:
通过对上面的分析我们可以知道如下几点:
1. 命令模式是通过命令发送者和命令执行者的解耦来完成对命令的具体控制的。
2. 命令模式是对功能方法的抽象,并不是对对象的抽象。
3. 命令模式是将功能提升到对象来操作,以便对多个功能进行一系列的处理以及封装。