分析:命令模式

关于 命令模式 介绍的参考资料:(1)命令模式(2)研磨设计模式之 命令模式-1

从以上参考资料上我们可以知道:

命令模式的定义将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

要如何简单的理解这句话呢?(1)将一个请求封装为一个对象--请求对象化,为什么提出这个设计呢?可能是因为我们之前把请求做成了一个方法了,现在呢,把方法提成对象(method-->Object);(2)从而可以使不同的请求对客户进行参数化--这是命令模式达到的有益效果,为什么能达到这样的效果呢?如果之前我们把命令表现为方法的话,如果我们新添加一个命令,我们是不是需要修改源文件?那不就违反了开闭原则了!现在把方法抽象为一个对象,并可以参数化,这就大大提高了命令的多样性和系统的灵活性了。(3)对请求排队或记录请求日志--这可能是说使用命令模式后,更加容易操作这些(具体如何理解我还比较模糊);(4)支持可撤销的操作--如果理解的了上面一个,这个就比较好理解了吧,如果请求能够排队,撤销就很容易实现了。

如果还是不好理解的话,我们先看个现实生活中的例子:开电脑。

对于使用电脑的客户——就是我们来说,开机确实很简单,按下启动按钮,然后耐心等待就可以了。但是当我们按下启动按钮过后呢?谁来处理?如何处理?都经历了怎样的过程,才让电脑真正的启动起来,供我们使用。
   先一起来简单的认识一下电脑的启动过程,了解一下即可。
  • 当我们按下启动按钮,电源开始向主板和其它设备供电
  • 主板的系统BIOS(基本输入输出系统)开始加电后自检
  • 主板的BIOS会依次去寻找显卡等其它设备的BIOS,并让它们自检或者初始化
  • 开始检测CPU、内存、硬盘、光驱、串口、并口、软驱、即插即用设备等等
  • BIOS更新ESCD(扩展系统配置数据),ESCD是BIOS和操作系统交换硬件配置数据的一种手段
  • 等前面的事情都完成后,BIOS才按照用户的配置进行系统引导,进入操作系统里面,等到操作系统装载并初始化完毕,就出现我们熟悉的系统登录界面了。  如果你想了解更多,请看Osc的科普:计算机是如何启动的?

把上面的过程总结一下,主要就这么几个步骤:首先加载电源,然后是设备检查,再然后是装载系统,最后电脑就正常启动了。可是谁来完成这些过程?如何完成?
不能让使用电脑的客户——就是我们来做这些工作吧,真正完成这些工作的是主板,那么客户和主板如何发生联系呢?现实中,是用连接线把按钮连接到主板上的,这样当客户按下按钮的时候,就相当于发命令给主板,让主板去完成后续的工作。
另外,从客户的角度来看,开机就是按下按钮,不管什么样的主板都是一样的,也就是说,客户只管发出命令,谁接收命令,谁实现命令,如何实现,客户是不关心的。

命令模式的结构图:

分析:命令模式

对象分析:

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个参考资料,小部分的个人观点中如有错误的地方,欢迎拍砖!

 

你可能感兴趣的:(命令模式)