关于 命令模式 介绍的参考资料:(1)命令模式(2)研磨设计模式之 命令模式-1
从以上参考资料上我们可以知道:
命令模式的定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
要如何简单的理解这句话呢?(1)将一个请求封装为一个对象--请求对象化,为什么提出这个设计呢?可能是因为我们之前把请求做成了一个方法了,现在呢,把方法提成对象(method-->Object);(2)从而可以使不同的请求对客户进行参数化--这是命令模式达到的有益效果,为什么能达到这样的效果呢?如果之前我们把命令表现为方法的话,如果我们新添加一个命令,我们是不是需要修改源文件?那不就违反了开闭原则了!现在把方法抽象为一个对象,并可以参数化,这就大大提高了命令的多样性和系统的灵活性了。(3)对请求排队或记录请求日志--这可能是说使用命令模式后,更加容易操作这些(具体如何理解我还比较模糊);(4)支持可撤销的操作--如果理解的了上面一个,这个就比较好理解了吧,如果请求能够排队,撤销就很容易实现了。
如果还是不好理解的话,我们先看个现实生活中的例子:开电脑。
对于使用电脑的客户——就是我们来说,开机确实很简单,按下启动按钮,然后耐心等待就可以了。但是当我们按下启动按钮过后呢?谁来处理?如何处理?都经历了怎样的过程,才让电脑真正的启动起来,供我们使用。 把上面的过程总结一下,主要就这么几个步骤:首先加载电源,然后是设备检查,再然后是装载系统,最后电脑就正常启动了。可是谁来完成这些过程?如何完成?
不能让使用电脑的客户——就是我们来做这些工作吧,真正完成这些工作的是主板,那么客户和主板如何发生联系呢?现实中,是用连接线把按钮连接到主板上的,这样当客户按下按钮的时候,就相当于发命令给主板,让主板去完成后续的工作。
另外,从客户的角度来看,开机就是按下按钮,不管什么样的主板都是一样的,也就是说,客户只管发出命令,谁接收命令,谁实现命令,如何实现,客户是不关心的。
命令模式的结构图:
对象分析:
Command:
定义命令的接口,声明执行的方法。
ConcreteCommand:
命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
Receiver:
接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
Invoker:
要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
Client:
创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。
代码实现:
(1)Command:命令的接口
public interface Command { void execute(); }
(2)ConcreteCommand:具体的命令
public class ConcreteCommand implements Command{ //持有相应的接受者对象 private Receiver receiver = null; public ConcreteCommand( Receiver receiver ){ this.receiver=receiver; } @Override public void execute() { receiver.action();//接受者对象的方法 } }
(3)Receiver:命令接受者、真正执行命令
public class Receiver { public void action(){ System.out.println("执行操作"); } }
(4)Invoker:发命令者,会有各种各样的命令
public class Invoker { private Command command= null; public Invoker(Command command){ this.command = command; } public void action(){ command.execute(); } }
(5) Client:测试类
public class Client{ public static void main(String[] args) { //创建接收者 Receiver receiver = new Receiver(); //创建命令对象,设定它的接收者 Command command = new ConcreteCommand(receiver); //创建请求者,把命令对象设置进去 Invoker invoker = new Invoker(command); //执行方法 invoker.action();//本质:invoker调用command里的execute(),execute()的功能是接收命令并让Receiver执行命令 } }
执行效果:
执行操作
我们不禁反问:“我们写了这么多程序就出现了这样的结果?!”答:“是的!”那我们还写这么多程序干嘛,直接调用Receiver的action() 不就可以了嘛!!!回到刚才举的例子,我们用例子来描述这一逻辑吧!
Receiver就像电脑一样,虽然自己有开机、关机、挂起等方法,但是如果没有接收到外界的命令,是不会自己执行这些方法的(咱只讨论一般情况);Invoker——发起命令的人,可能会拥有各种各样的命令,比如开关机,挂起,重启等,将来还可能有别的命令……如果把这些命令抽象成方法的话,就不具有可扩展性而言,而如果把这些命令抽象成类,那情况就大不相同了,会使命令变得丰富多彩!因此在此模式下,Invoker持有Command对象--这也就是面向接口编程的极大优点!而为了能保证命令能被Receiver执行,则具体的命令ConcreteCommand则应该持有Receiver对象,最终达到命令传递并执行的目的。
组装过程的调用顺序示意图:
2.执行命令时的调用顺序示意图
注:文章内容大部分来源于我开头提到的2个参考资料,小部分的个人观点中如有错误的地方,欢迎拍砖!