命令模式把封装带到一个全新的境界,把方法调用封装起来。乍一听好像没什么特别的,不明白这个模式有什么特别的地方,下面就来详细看下。
场景描述
假设有一个遥控器,上面有N个插槽,插槽中可以插入接口匹配的电器,比如电灯、电风扇之类。每一个插槽对应两个开关:ON、OFF,外加一个整体共用的撤销按钮,会撤销最后一个按钮的动作。这个场景的关键问题在于遥控器并不知道每个电器的内部实现,但是插上去却能控制电器的开关 ,遥控器与电器完全解耦。下面就看下命令模式是如何实现的。
代码示例
下面将由浅入深,来逐步实现命令模式。
示例一:简化场景
把场景简化下,假设遥控器上只有一个按钮,同时只提供一个插槽,我们有一个电灯需要接上去。
先定义一个电灯,很简单,就提供一个开灯的方法。
12345public class Light {public void on() {System.out.println("Light is on");}}定义命令接口,并且提供一个开灯命令的实现。实现的构造函数中被传入了某个电灯,以便让命令控制。调用命令的
execute()
其实就是在调用命令请求接收者(此处就是Light对象)的on()
方法。123public interface Command {public void execute();}123456789101112131415public class LightOnCommand implements Command {/*** 请求接收者*/Light light;public LightOnCommand(Light light) {this.light = light;}public void execute() {light.on();}}定义简单的遥控器
12345678910111213141516171819202122public class SimpleRemoteControl {/*** 插槽,持有一个命令接口*/Command slot;/*** 设置插槽持有的命令** @param command*/public void setCommand(Command command) {slot = command;}/*** 按下按钮动作*/public void buttonWasPressed() {slot.execute();}}测试
12345678910public class Test {public static void main(String[] args) {SimpleRemoteControl remote = new SimpleRemoteControl();Light light = new Light();LightOnCommand lightOn = new LightOnCommand(light);remote.setCommand(lightOn);remote.buttonWasPressed();}}
这个简化的场景下,感觉Command接口可以省略,比如换成一个Electric接口,定义一个on()方法,电灯实现该接口,SimpleRemoteControl中直接注入Electric也可以实现。好像没命令那一层什么事儿,反而更简单了。可能是简化场景的错觉,又或者是没有真正理解命令模式,继续深入看看。
示例二:完善7个插槽的遥控器
这个例子我们把遥控器的7个插槽完善,并且多加一个音响电器试试。
定义电灯、音响类,需要注意的是Stereo添加了额外的方法,因为音响开启的方式跟电灯不一样。
123456789101112131415161718public class Light {/*** 电灯名称,用于区分是哪个房间的电灯*/private String name;public Light(String name) {this.name = name;}public void on() {System.out.println(name + " light is on");}public void off() {System.out.println(name + " light is off");}}1234567891011121314151617public class Stereo {public void on() {System.out.println("Stereo on");}public void off() {System.out.println("Stereo off");}public void setCD() {System.out.println("Stereo set CD");}public void setVolume(int volume) {System.out.println("Stereo set volume " + volume);}}定义相关命令,这里我们定义了一组电灯开关命令和一组音响开关命令
123public interface Command {public void execute();}123456789101112131415public class LightOnCommand implements Command {/*** 请求接收者*/Light light;public LightOnCommand(Light light) {this.light = light;}public void execute() {light.on();}}123456789101112131415public class LightOffCommand implements Command {/*** 请求接收者*/Light light;public LightOffCommand(Light light) {this.light = light;}public void execute() {light.off();}}1234567891011121314public class StereoOnWithCDCommand implements Command {Stereo stereo;public StereoOnWithCDCommand(Stereo stereo) {this.stereo = stereo;}public void execute() {stereo.on();stereo.setCD();stereo.setVolume(111);}}123456789101112public class StereoOffCommand implements Command {Stereo stereo;public StereoOffCommand(Stereo stereo) {this.stereo = stereo;}public void execute() {stereo.off();}}定义遥控器,完善7个插槽
12345678910111213141516171819202122public class RemoteControl {Command[] onCommands;Command[] offCommands;public RemoteControl() {this.onCommands = new Command[7];this.offCommands = new Command[7];}public void setCommand(int slot, Command onCommand, Command offCommand) {onCommands[slot] = onCommand;offCommands[slot] = offCommand;}public void onButtonWasPressed(int slot) {onCommands[slot].execute();}public void offButtonWasPressed(int slot) {offCommands[slot].execute();}}测试
12345678910111213141516171819202122232425262728293031public class Test {public static void main(String[] args) {RemoteControl remoteControl = new RemoteControl();// 电器类Light livingRoomLight = new Light("Living Room");Light kitchenLight = new Light("Kitchen");Stereo stereo = new Stereo();// 命令类LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);LightOffCommand kichenLightOff = new LightOffCommand(kitchenLight);StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(stereo);StereoOffCommand stereoOff = new StereoOffCommand(stereo);// 遥控器remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);remoteControl.setCommand(1, kitchenLightOn, kichenLightOff);remoteControl.setCommand(2, stereoOnWithCD, stereoOff);// 测试开关remoteControl.onButtonWasPressed(0);remoteControl.offButtonWasPressed(0);remoteControl.onButtonWasPressed(1);remoteControl.offButtonWasPressed(1);remoteControl.onButtonWasPressed(2);remoteControl.offButtonWasPressed(2);}}
在这个场景中,体会到命令模式的好处了。因为我们添加的音响有一个比较复杂的开启过程,其他电器可能更复杂,无法抽象出一个统一的行为。使用命令模式的话,遥控器只要知道插入插槽的命令是什么,执行execute()
方法就可以了,而不必知道每个电器开、关的细节。
示例三:最后一步,加上撤销功能
先来讲下撤销功能的作用:比如收客厅的电灯是关闭的,然后按下遥控器上的开启按钮,电灯就被打开了。现在如果按下撤销按钮,那么上一个动作将被倒转。在这个例子中,电灯将被关闭。下面同样以简单的方式实现。
定义电灯
123456789public class Light {public void on() {System.out.println("Light is on");}public void off() {System.out.println("Light is off");}}定义命令接口,以及电灯开、关命令的实现。这次在接口中添加了一个undo方法,用于撤销时执行的操作。
12345678public interface Command {public void execute();/*** 给命令接口添加undo方法*/public void undo();}1234567891011121314151617181920public class LightOnCommand implements Command {Light light;public LightOnCommand(Light light) {this.light = light;}public void execute() {light.on();}/*** execute()是打开电灯,所以undo()该做事情就是关闭电灯*/public void undo() {light.off();}}1234567891011121314151617181920public class LightOffCommand implements Command {Light light;public LightOffCommand(Light light) {this.light = light;}public void execute() {light.off();}/*** undo()把电灯打开*/public void undo() {light.on();}}定义支持撤销按钮的遥控器。在这里添加了undoCommand,用于记录最后一次执行的操作是什么。具体undo的逻辑由各个命令实现,遥控器不关注。
1234567891011121314151617181920212223242526272829303132333435public class RemoteControlWithUndo {Command[] onCommands;Command[] offCommands;Command undoCommand;public RemoteControlWithUndo() {this.onCommands = new Command[7];this.offCommands = new Command[7];}public void setCommand(int slot, Command onCommand, Command offCommand) {onCommands[slot] = onCommand;offCommands[slot] = offCommand;}/*** 按下按钮时先执行命令* 然后将该命令记录在undoCommand变量中** @param slot*/public void onButtonWasPressed(int slot) {onCommands[slot].execute();undoCommand = onCommands[slot];}public void offButtonWasPressed(int slot) {offCommands[slot].execute();undoCommand = offCommands[slot];}public void undoButtonWasPressed() {undoCommand.undo();}}测试
123456789101112131415public class Test {public static void main(String[] args) {RemoteControlWithUndo remoteControlWithUndo = new RemoteControlWithUndo();Light light = new Light();LightOnCommand lightOnCommand = new LightOnCommand(light);LightOffCommand lightOffCommand = new LightOffCommand(light);remoteControlWithUndo.setCommand(0, lightOnCommand, lightOffCommand);remoteControlWithUndo.onButtonWasPressed(0);remoteControlWithUndo.offButtonWasPressed(0);remoteControlWithUndo.undoButtonWasPressed();}}
命令模式到这里就学完了,这个模式下明显的感受是尽量解耦,让一个类的功能尽量单一,比如说控制电灯的命令抽象成开、关两个命令,而不是一个集合了开、关的命令。当然这只是个人主观的感受,仅供参考。