首页 > 代码库 > 设计模式(六)The Command Pattern 命令模式
设计模式(六)The Command Pattern 命令模式
问题引入
近来,智能家居闹得比较凶,这里我们想要实现一个简单的自动家居,由一个遥控器来完成电灯、音响、风扇的开关。
模式定义
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作
认识模式
对于这个模式我最欣赏的就是他将“做什么”与"怎么做"的问题或者说是“动作的请求者”与"动作的实现者”进行了完美的解耦。并且,它支持undo的操作。
问题解决
命令模式实现步骤:
* 1、定义一个Command接口,这个接口中只含有execute()方法;
* 2、定义各个命令对象(需要实现上述的Command接口),它包含具体实现功能的对象引用,并在execute()方法中定义相应的操作;
* 3、将这个命令对象交给”动作请求者“。
直接上代码:
一、不带undo的遥控器
1>这是Command接口
1 2 3 4 5 6 7 | package my.oschina.net.design.commands; public interface Com { public void execute(); //这个undo()撤销方法我们一会儿再说 public void undo(); } |
2>我们来定义一个开(关)灯的命令
先看看这个灯的类吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package my.oschina.net.design.commands; /** * 他包括灯的一切操作(关灯、开灯) * @author Eswin * */ public class Light { public Light(){} public void lightOn() { System.out.println( "The light is on !" ); } public void lightOff() { System.out.println( "The light is off !" ); } } |
这是开关灯的命令对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package my.oschina.net.design.commands; public class LightOnCommand implements Com{ private Light light; public LightOnCommand(Light light) { this .light = light; } public void execute() { light.lightOn(); } @Override //显然开灯的撤销就是要关灯 public void undo() { // TODO Auto-generated method stub light.lightOff(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package my.oschina.net.design.commands; public class LightOffCommand implements Com{ private Light light; public LightOffCommand(Light light) { this .light = light; } public void execute() { light.lightOff(); } @Override //显然开灯的撤销就是要开灯 public void undo() { // TODO Auto-generated method stub light.lightOn(); } } |
同理对于fans也是一样的
3>是时候告诉”接收者“遥控器他可以实现的命令了
这是一个简单遥控器类(只有一个按钮,我们只是来测试这个命令,下面有个更全面的例子)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package my.oschina.net.design.commands; public class ControlBar { //命令对象的引用 Com com; public ControlBar(){} /* * 通过传入Com参数决定遥控器可以实现的命令 */ public void setCommand(Com com) { this .com = com; } //一旦我们按下按钮(下命令),就有真正的接受者完成相应的任务,我们的遥控器并不需要知道到底是如何实现的 public void pressDown() { com.execute(); } } |
Test一下吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | package my.oschina.net.design.commands; public class TestCom { public static void main(String[] args) { Light light = new Light(); Com com1 = new LightOnCommand(light); Com com2 = new LightOffCommand(light); Fans fans = new Fans(); Com com3 = new FansOnCommand(fans); Com com4 = new FansOffCommand(fans); ControlBar clb = new ControlBar(); clb.setCommand(com1); clb.pressDown(); clb.setCommand(com2); clb.pressDown(); clb.setCommand(com3); clb.pressDown(); clb.setCommand(com4); clb.pressDown(); } } |
可以看到我们设置好命令,然后只需要PressDown按钮,就可以完成相应的命令
结果如下:
二、带有undo的遥控器
这是一个很炫酷的功能。你肯定遇到过这样尴尬的情况,当你做下一个决定的那一刻你就发现自己后悔了(很奇妙),怎么办???没事,我们提供了undo功能!
undo说到底很简单,就是撤销上步操作(没错,就是ctrl + z),换句话说,就是执行与你上一步的动作相反的动作(你开了我就关)
回头看看LightOnCommand和LightOffCommand命令中的undo操作!确实很简单!
好了,我们可以在遥控器上加上这个硬件按钮了。
接下来我们创建一个真实的遥控器,遥控器上有多个控制按钮,分别实现各个功能。
这是遥控器类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | package my.oschina.net.design.commands; import java.util.Stack; public class ControlBar2 { //我们将命令放在数组中更加清晰一点 Com[] oncoms; Com[] offcoms; //这是个撤销命令对象的引用 Com undoCom; //用栈来保存每次需要撤销的操作,从而可以一直undo(当然也是有一定次数限制的) public Stack<Com> in_undo_satck = new Stack<Com>(); ControlBar2() { //记得生成这个数组,要不然出现空指针的现象 oncoms = new Com[ 3 ]; offcoms = new Com[ 3 ]; } /** * * @param i 遥控器的命令插槽,用于标识控制的是什么(电灯?电扇?) * @param oncom 控制对象的开命令 * @param offcom 控制对象的关命令 */ public void setCommand( int i, Com oncom, Com offcom) { oncoms[i] = oncom; offcoms[i] = offcom; } //开按钮 public void pressOn( int i) { oncoms[i].execute(); 记录下上一次执行的动作, undoCom = oncoms[i]; //将上一次将执行的动作记录到栈中,一会弹出一次调用undo()方法 in_undo_satck.push(undoCom); } //关按钮 public void pressOff( int i) { offcoms[i].execute(); undoCom = offcoms[i]; //将上一次将执行的动作记录到栈中,一会弹出一次调用undo()方法 in_undo_satck.push(undoCom); } /*带参数的undo撤销按钮,根据传入的i(标识控制哪个对象),对相应的开操作做撤销,这个可以不要 public void Pressundo(int i) { oncoms[i].undo(); in_undo_satck.push(undoCom); } */ //undo撤销按钮(终于见到你了) public void Pressundo() { //将上一次将执行的动作记录到栈中,一会弹出一次调用undo()方法,撤销的命令也可以压入栈中,进行撤销操作 if (!in_undo_satck.isEmpty()) in_undo_satck.pop().undo(); } } |
好了,Test一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | package my.oschina.net.design.commands; public class TestControlBar2 { public static void main(String[] args) { // TODO Auto-generated method stub //创建一个Light对象 Light light = new Light(); //用Light对象初始化LightOnComman与LightOffCommand命令对象 Com com1 = new LightOnCommand(light); Com com2 = new LightOffCommand(light); //创建一个Fans对象 Fans fans = new Fans(); //用Light对象初始化FansOnComman与FansOffCommand命令对象 Com com3 = new FansOnCommand(fans); Com com4 = new FansOffCommand(fans); //这是一个遥控器 ControlBar2 clb = new ControlBar2(); //在各个插槽上绑定命令 clb.setCommand( 0 , com1, com2); clb.setCommand( 1 , com3, com4); //记住了我们绑定的命令0上绑定的是对Light的操作 clb.pressOn( 0 ); clb.pressOff( 0 ); clb.pressOn( 1 ); clb.pressOff( 1 ); clb.pressOn( 1 ); clb.pressOn( 0 ); clb.pressOn( 1 ); clb.pressOff( 1 ); System.out.println( "UNDO ------------> " ); while ( clb.in_undo_satck.iterator().hasNext()) clb.Pressundo(); } } |
预测结果:
从分割线(UNDO --------------->)处,两边是成镜像对称的。
实际结果(丝毫不差):
好了,到这里为止,命令模式差不多了。
不过这里还有一个问题,就是风扇的撤销是不是太过简陋了?的确,风扇除了开就是关?显然不是嘛!我们可是可以调风速的呀!
这个问题其实也很好解决,我们在只要在设计风扇的命令的时候添加一个Speed的字段,记录下设置风速之前的Speed,撤销的时候再调回去就OK啦!
模式延伸
对于大多数请求-响应模式的功能,比较适合使用命令模式。正如命令模式定义说的那样,命令模式对实现记录日志、撤销操作等功能比较方便。
设计模式(六)The Command Pattern 命令模式