我时常以为,所谓的“设计模式”,更多像是一种方法论,而不应该算作所谓的“知识点”。这些就像高中数学有专门的解题方法集合,形如“不等式逼等式”(证明一个未知数大于等于一个值,同时又证明这个未知数小于等于这个值,那么这个未知数只能是这个值)这样的方法一般,根本没法说得非常具体。而这才是所谓“知识”大放异彩的地方,没有这些无数人总结出来的具体的方法,让我们通过个人总结对现有的知识点进行链接来形成一套,哪怕只是一个小小的方法,或者模式,都是很难得的。如果将学高中数学的方法论做一个合理的外推,那么程序设计中也就像是用一些知识点(一门或多门变成语言)来串接起来解决一个问题,在解决问题的过程中,通过一系列方法(模式)的运用,来获得一个最佳实践,也可以说是数学难题给出的推荐答案。但程序设计与数学难题不同的地方在于,数学难题的某些步骤可以暂时跳过,或者说,即使你没有完全解出题目,也可以完成你暂时明了的工作,而程序设计是一个“非1即0”的过程,“what you see is what you get”,一个小小的错误,往往可以拖崩整个程序,哪怕只是百分之一的错误,也完全可以让已完成的百分之九十九毫无价值。好在作为一个学习者,目前碰到的问题,往往都有以往的解决方案提供,简言之,因为菜,所以先专注于积累,等到你真的拆了千万轮子,见过各种实现,可以挥洒自如时,自然可以写出自己的感悟,那种高度不知比初学时的钻牛角尖高了多少个level!
命令模式(Command Pattern),在gof中的定义是这样的“Encapsulate a request as an object, thereby allowing for the parameterization of clients with different requests, and the queuing or logging of requests. It also allows for the support of undoable operations.”没错,“将请求分装成对象,然后用不同的请求,队列或日志来参数化对象,同时也支持可撤销的操作”,这里的重点在于“请求”的封装,正如我在上文所提,所有的设计模式(解决方法)都是应用于具体问题的解决过程的,我们参考Head First的案例,开始我们的故事。
假设我们现在要设计一个遥控器,这个遥控器具有多个可编程的插槽,我们希望每一个插槽都能控制一个或一组家电装置(电视,电灯,音响啥的),每一个插槽都对应有一个开关,同时还有一个总的撤销(undo)按钮来进行退回,怎么来设计这个遥控器呢?
这个问题的难点在于,每一个特定的家电装置,其所谓开关都是不一样的,比如电视就是简单的开关,而风扇则涉及到风速的大小(想想我们平时的风扇,开/风速都是设计到一个按钮上),音响就涉及到立体声啊,低重音啊之类的开关... ...在我们这个遥控器中,每一个开关按下去,往往会产生一系列的措施,然而对于我们的遥控器来说,它根本不需要知道这些实现的细节,只要知道,我有一个关于“打开风扇”的“请求”过来,我就执行,即把我们的请求封装成一个特定的对象,当按钮按下时,就可以请求对象来做相关的工作,然后故事就结束了,风扇这个对象和遥控器就这么解耦了,将“动作的执行者”和“动作的请求者”解耦,也就是命令模式的精髓所在。
那么怎么来做呢?我们设计一个Command借口,其只有一个方法,execute(),对应我们的风扇,我们通过一个FanHighCommand来实现这个接口,表示已高速来打开风扇。然后,我们写一个遥控器remote,它有一个setCommand方法和一个buttonWasPressed方法,前者用来设置那个方法,后者表示调用方法。这里可以看出命令模式的思想,也就是,这个遥控器压根就不管command是什么,只要将command封装传进来,然后就执行即可,至于执行的过程,写在的继承自command的方法里(比如这里我们把高速打开风的过程的实现写在了FanHighCommand里);
当要进行一些列操作时(比如一键“轰趴模式”),我们只要将这一系列的执行过程(音响开,低重音起,灯光暗淡,TV开)分别写进每一个电器的执行过程中,然后将这一系列的command封装好未一个HomePartyCommand,传染remote的setCommand中,就可以一键轰趴,有木有比较炫?undo的实现其实比较简单,读者可以自己想想怎么弄。
话说回来,笔者的代码经验还是太少,几乎没有用过命令模式,这种将运算快打包传来传去的方法,似乎用在“队列请求”或“日志请求”中比较合适,一个一个调用,执行只管execute,压根就不管具体怎么玩,是一种不错的解耦吧!