命令模式

命令模式的定义:将请求封装为对象,那么就可以对这些请求对象进行排队或记录请求日志,也可以对请求进行撤销、恢复,还可以用不同的请求对客户进行参数化。

我们来看一下标准的命令模式的结构图:

我们来设想一个我们去餐厅吃饭点餐的情景,可以看出,我们就是(Client),我们拿过来菜单点了很多菜,就是发出了很多命令,因此我们点餐这些请求可以被封装成为一个个请求对象(ConcreteCommand),这些请求对象都会被厨房的某个厨师接收到(每个请求对象都会有一个真正执行命令的接受者,请求对象在内部会调用接受者的方法),因此厨师就是接受者(Receiver),我们点的菜都被服务员记录在了一张小白纸上,因此这张小白纸就是(Invoker:通常会持有请求对象,可以持有很多请求对象),【ps:有时候餐厅会推出套餐,我们也可以点这个套餐,也就是一个请求对象里面有多个请求对象,我们把这种叫做宏命令,就是包含多个命令的命令】 ,这张白纸被送到厨房后,厨师拿过来一看,就会触发Invoker.runCommand()—>ConcreteCommand.execute()—>Receiver.action() 最终做完菜,呈现给我们。

我们继续来想象一些情形,去餐厅吃饭的不可能只有我们自己,同时会有很多人去,因此厨房的小白纸会很多,厨师也会有很多,会有这样的情景,厨师都去拿一张小白纸,做菜,做完了,又去拿一张,这就是命令模式定义里提到的请求队列。我们可以看下这个例子里的命令模式的结构图:

这个跟之前的的例子不同之处在于,不是由Invoker.runCommand来触发执行所有的命令,而是通过Receiver.run,其中run是多线程方法

public void run()
{
    //开启了一个新线程执行run方法,while里这个厨师不停的去看小白纸上的菜,然后去做
    while(true)
    {
        Command cmd = CommandQueue.getFirstCommand();
        //与之前的例子不同,该请求对象在接收者自身方法里设置接收者(因为之前也不知道哪个厨师做这道菜)
        cmd.setReceiver(this);
        //开始做菜,最终会调用厨师.Cook()方法
        cmd.execute();
    }
}

CommandQueue就相当于一个放所有小白条的地方,它的getFirstCommand方法就是获取commands里的第一个请求对象,然后从队列中移除该请求对象。

客户开始点菜的时候,点了两道菜,于是这两道菜被记在了小白条上,然后客户说addOver(),小白条就被送到厨房了,这两道菜就被加到了CommandQueue.commands里面。

然后,我们再来设想一下,万一厨师们做着做着菜,小白条们被风吹跑了怎么办?难道再让客户重新点餐?就算重新点餐,那谁知道哪些已经做了,哪些还没做?现实世界中貌似没有好的解决方案。但在程序里,我们可以使用记录请求日志的方式来解决。一种办法就是利用序列化与反序列化来实现,由于请求都被封装成了对象,所以CommandQueue.commands是可以被序列化与反序列化的,试想,每一次操作完CommandQueue.commands都对其序列化保存到文件,下次操作时从文件反序列化成CommandQueue.commands。或者将每一次操作后的CommandQueue.commands都对其序列化保存到不同的文件,不就是记录请求日志吗? 由于记录了每次的请求,所以做撤销、恢复操作就有了可能。而这种可撤销操作属于存储恢复式。还有一种是补偿式,又叫反操作式,即本来是加的操作,那撤销就执行减的操作,本来是打开的操作,撤销就执行关闭的操作。

命令模式的本质:封装请求。

原文地址:https://www.cnblogs.com/hanmeimei/p/4644780.html