设计模式之命令模式(二)
上一次留给大家去做的实践,不知道大家执行的怎么样了呢。
我们通过一个简单的练习,完成了一个控制开关。那现在,我们打算将遥控器的每个插槽,对应到一个命令这样就要遥控器变成“调用者”。当按下按钮,相应命令对象的execute()方法就会被调用,其结果就是,接收者(例如电灯、风扇、音响)的动作被调用。
实现遥控器
public class remotecontrol { command[] oncommands; command[] offcommands; public remotecontrol() { oncommands = new command[7]; offcommands = new command[7]; // 在构造器中,只需实例化并初始化这两个开与关的数组 command nocommand = new nocommand(); for (int i = 0; i < 7; i++) { oncommands[i] = nocommand; offcommands[i] = nocommand; } } // 这个方法有三个参数,分别是插槽的位置、开的命令、关的命令。这些命令将记录开关数组中对应的插槽位置,以供稍后使用 public void setcommand(int slot, command oncommand, command offcommand) { oncommands[slot] = oncommand; offcommands[slot] = offcommand; } // 当按下开或关的按钮,硬件就会负责调用对应的方法,也就是onbuttonwaspushed或offbuttonwaspushed public void onbuttonwaspushed(int slot) { oncommands[slot].execute(); } public void offbuttonwaspushed(int slot) { offcommands[slot].execute(); } public string tostring() { stringbuffer stringbuff = new stringbuffer(); stringbuff.append("\n------ remote control -------\n"); for (int i = 0; i < oncommands.length; i++) { stringbuff.append("[slot " + i + "] " + oncommands[i].getclass().getname() + " " + offcommands[i].getclass().getname() + "\n"); } return stringbuff.tostring(); } }
实现命令
此前我们已经动手实现过lightoncommand,纯粹就是简单的开和关命令。那现在,我们来为音响编写开与关的命令。
音响的关闭是毫无难度,就是开启的时候有点复杂,你知道为什么吗?难道音响开了就好了?是否还需要后续其他的动作才能让音响响起来了?哎呀,小编多嘴了好像。
public class stereoonwithcdcommand implements command { stereo stereo; public stereoonwithcdcommand(stereo stereo) { this.stereo = stereo; } // 打开音响,需要三个步骤,开启音响,设置cd播放,设置音量,不然就成哑巴了 public void execute() { stereo.on(); stereo.setcd(); stereo.setvolume(11); } }
这里列举了一个电灯,一个音响,差不多就把其他类似的都已经搞定了,比如电扇、门,对吧。所以,赶紧看看你之前动手的操作,是不是和小编的差不多。
让我们继续看下,多个的是怎么实现的呢。
public class remoteloader { public static void main(string[] args) { remotecontrol remotecontrol = new remotecontrol(); // 将所有的装置创建在合适的位置 light livingroomlight = new light("living room"); light kitchenlight = new light("kitchen"); ceilingfan ceilingfan= new ceilingfan("living room"); garagedoor garagedoor = new garagedoor(""); stereo stereo = new stereo("living room"); // 创建所有的电灯命令对象 lightoncommand livingroomlighton = new lightoncommand(livingroomlight); lightoffcommand livingroomlightoff = new lightoffcommand(livingroomlight); lightoncommand kitchenlighton = new lightoncommand(kitchenlight); lightoffcommand kitchenlightoff = new lightoffcommand(kitchenlight); // 创建吊扇的开与关命令 ceilingfanoncommand ceilingfanon = new ceilingfanoncommand(ceilingfan); ceilingfanoffcommand ceilingfanoff = new ceilingfanoffcommand(ceilingfan); // 创建车库门的上与下命令 garagedoorupcommand garagedoorup = new garagedoorupcommand(garagedoor); garagedoordowncommand garagedoordown = new garagedoordowncommand(garagedoor); // 创建音响的开与关命令 stereoonwithcdcommand stereoonwithcd = new stereoonwithcdcommand(stereo); stereooffcommand stereooff = new stereooffcommand(stereo); // 现在已经有了全部的命令,我们将它们加载到遥控器插槽中 remotecontrol.setcommand(0, livingroomlighton, livingroomlightoff); remotecontrol.setcommand(1, kitchenlighton, kitchenlightoff); remotecontrol.setcommand(2, ceilingfanon, ceilingfanoff); remotecontrol.setcommand(3, stereoonwithcd, stereooff); system.out.println(remotecontrol); // 在这里逐步按下每个插槽的开与关按钮 remotecontrol.onbuttonwaspushed(0); remotecontrol.offbuttonwaspushed(0); remotecontrol.onbuttonwaspushed(1); remotecontrol.offbuttonwaspushed(1); remotecontrol.onbuttonwaspushed(2); remotecontrol.offbuttonwaspushed(2); remotecontrol.onbuttonwaspushed(3); remotecontrol.offbuttonwaspushed(3); } }
写文档的时候到了
我们这个主要的设计目标就是让遥控器代码尽可能地简单,这样一来,新的厂商类一旦出现,遥控器并不需要随之修改。因为,我们才用了命令模式,从逻辑上将遥控器的类和厂商的类解耦。我们相信这将降低遥控器的生产成本,并大大地减少维护时所需的费用。
下面的类图提供了设计的全貌:
撤销哪去了?
别急别急,小编说的功能都会有的。撤销功能使用起来就是这样的:比如说客厅的电灯是关闭的,然后你按下遥控器上的开启按钮,自然电灯就被打开了。现在如果按下撤销按钮,那么上一个动作将被倒转,在这个例子里,电灯将被关闭。
同样,我们先来一个简单的撤销示例。之前我们用的是execute()方法实现开启或者关闭的调用,那么我们用undo()方法来执行撤销操作。即在command接口里实现一个同execute()相反的方法undo(),然后在实现类里将undo()的动作做成和execute()相反的操作即可。
讲的有点笼统?在这里小编就不提供具体的代码了,详细的请看github我的分享吧。
使用状态实现撤销
因为电灯这个开关已经撤销,是很简单的入门,小编没有提供源码在文中,但是因为还有电风扇这个存在,小编还不得不继续搞一个高大上的方式。电扇不仅仅是开关,还有档位的存在,对吧,是不是瞬间有思路了呢?
public class ceilingfan { public static final int high = 3; public static final int medium = 2; public static final int low = 1; public static final int off = 0; string location; int speed; public ceilingfan(string location) { this.location = location; speed = off; } public void high() { speed = high; system.out.println(location + " ceiling fan is on high"); } public void medium() { speed = medium; system.out.println(location + " ceiling fan is on medium"); } public void low() { speed = low; system.out.println(location + " ceiling fan is on low"); } public void off() { speed = off; system.out.println(location + " ceiling fan is off"); } public int getspeed() { return speed; } }
现在我们就来实现风扇的撤销。这么做,需要追踪吊扇的最后设置速度,如果undo方法被调用了,就要恢复成之前吊扇速度的设置值。就如下面这样:
public class ceilingfanhighcommand implements command { ceilingfan ceilingfan; // 增加局部状态以便追踪吊扇之前的速度 int prevspeed; public ceilingfanhighcommand(ceilingfan ceilingfan) { this.ceilingfan = ceilingfan; } public void execute() { // 我们改变吊扇的速度之前,需要先将它之前的状态记录起来,以便需要撤销时使用 prevspeed = ceilingfan.getspeed(); ceilingfan.high(); } // 将吊扇的速度设置会之前的值,达到撤销的目的 public void undo() { if (prevspeed == ceilingfan.high) { ceilingfan.high(); } else if (prevspeed == ceilingfan.medium) { ceilingfan.medium(); } else if (prevspeed == ceilingfan.low) { ceilingfan.low(); } else if (prevspeed == ceilingfan.off) { ceilingfan.off(); } } }
让我们来测试下风扇吧
条件都具备了,那我们来测试下吧。我们打算把0号插槽的开启按钮设置为中速,把第1号插槽的开启按钮设置成高速,代码如下:
public class remoteloader { public static void main(string[] args) { remotecontrolwithundo remotecontrol = new remotecontrolwithundo(); ceilingfan ceilingfan = new ceilingfan("living room"); ceilingfanmediumcommand ceilingfanmedium = new ceilingfanmediumcommand(ceilingfan); ceilingfanhighcommand ceilingfanhigh = new ceilingfanhighcommand(ceilingfan); ceilingfanoffcommand ceilingfanoff = new ceilingfanoffcommand(ceilingfan); remotecontrol.setcommand(0, ceilingfanmedium, ceilingfanoff); remotecontrol.setcommand(1, ceilingfanhigh, ceilingfanoff); // 首先,我们以中速开启吊扇 remotecontrol.onbuttonwaspushed(0); // 然后关闭 remotecontrol.offbuttonwaspushed(0); system.out.println(remotecontrol); // 撤销,应该会回到中速 remotecontrol.undobuttonwaspushed(); // 这个时候开启高速 remotecontrol.onbuttonwaspushed(1); system.out.println(remotecontrol); // 再进行一次撤销,应该会回到中速 remotecontrol.undobuttonwaspushed(); } }
好了,至此我们不仅仅实现了单个的开与关,还实现了一整个遥控器所有控件的开与关,甚至是复杂的家电的开与关(音响、电扇的开启略复杂),而且均实现了撤销。作为程序员的你是不是经常使用撤销功能呢,反正我是经常使用的噢。
但是,这还不是终极状态。我们在这里只能实现一个家电的开与关,如果光凭按下一个按钮,不能实现灯光、电视、音响的同步使用,那这个遥控器对我们来说是不是还是有点low呢?是吧,确实有点low,如何破解,敬请期待我们的下一篇。
爱生活,爱学习,爱感悟,爱挨踢