应对软件需求变化-命令模式的应用
一、缘起
在软件构建过程中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合--比如需要对行为进行“记录、撤销/重做(undo/redo)、事务”等处理,这种无法抵御变化的紧耦合是不适合的。
在这种情况下,如何将“行为请求者”与“行为实现者”解耦?
将一组行为抽象为对象,可以实现两者之间的松耦合。
二、定义
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
三、应用举例
定义一个遥控器作为行为请求者,遥控器有多个按钮,每个按钮对应到一个命令。当按下按钮,相应命令对象的execute()方法就会被调用,其结果就是,行为实现者(例如:电灯、天花板电扇、音响)的动作被调用。
实现遥控器:
import java.util.*; // // This is the invoker // public class RemoteControlWithUndo { Command[] onCommands; //遥控器处理多个开与关的命令,使用相应数组记录这些命令。另外还要处理取消命令。 Command[] offCommands; Command undoCommand; public RemoteControlWithUndo() { //在构造器中,只需实例化并初始化这两个开关数组和取消命令。 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; } undoCommand = noCommand; } public void setCommand(int slot, Command onCommand, Command offCommand) { onCommands[slot] = onCommand; //方法有三个参数,分别是插槽的位置、开的命令、关的命令。这些命令将记录在开关数组中对应的插槽位置,以供稍后使用。 offCommands[slot] = offCommand; } public void onButtonWasPushed(int slot) { onCommands[slot].execute(); //当按下开或关的按钮,硬件就会负责调用对应的方法。同时设置可以取消的命令。 undoCommand = onCommands[slot]; } public void offButtonWasPushed(int slot) { offCommands[slot].execute(); undoCommand = offCommands[slot]; } public void undoButtonWasPushed() { undoCommand.undo(); } 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"); } stringBuff.append("[undo] " + undoCommand.getClass().getName() + "\n"); return stringBuff.toString(); } }
命令接口:
public interface Command { public void execute(); public void undo(); }
实现命令。开灯命令:
public class LightOnCommand implements Command { Light light; int level; public LightOnCommand(Light light) { this.light = light; } public void execute() { level = light.getLevel(); light.on(); } public void undo() { light.dim(level); } }
关灯命令:
public class LightOffCommand implements Command { Light light; int level; public LightOffCommand(Light light) { this.light = light; } public void execute() { level = light.getLevel(); light.off(); } public void undo() { light.dim(level); } }
开调光器命令:
public class DimmerLightOnCommand implements Command { Light light; int prevLevel; public DimmerLightOnCommand(Light light) { this.light = light; } public void execute() { prevLevel = light.getLevel(); light.dim(75); } public void undo() { light.dim(prevLevel); } }
关调光器命令:
public class DimmerLightOffCommand implements Command { Light light; int prevLevel; public DimmerLightOffCommand(Light light) { this.light = light; prevLevel = 100; } public void execute() { prevLevel = light.getLevel(); light.off(); } public void undo() { light.dim(prevLevel); } }
吊扇开小风命令:
public class CeilingFanLowCommand implements Command { CeilingFan ceilingFan; int prevSpeed; public CeilingFanLowCommand(CeilingFan ceilingFan) { this.ceilingFan = ceilingFan; } public void execute() { prevSpeed = ceilingFan.getSpeed(); ceilingFan.low(); } 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(); } } }
吊扇开中风命令:
public class CeilingFanMediumCommand implements Command { CeilingFan ceilingFan; int prevSpeed; public CeilingFanMediumCommand(CeilingFan ceilingFan) { this.ceilingFan = ceilingFan; } public void execute() { prevSpeed = ceilingFan.getSpeed(); ceilingFan.medium(); } 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(); } } }
吊扇开大风命令:
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(); } } }
关吊扇命令:
public class CeilingFanOffCommand implements Command { CeilingFan ceilingFan; int prevSpeed; public CeilingFanOffCommand(CeilingFan ceilingFan) { this.ceilingFan = ceilingFan; } public void execute() { prevSpeed = ceilingFan.getSpeed(); ceilingFan.off(); } 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(); } } }
默认命令:
public class NoCommand implements Command { public void execute() { } public void undo() { } }
行为实现者。灯:
public class Light { String location; int level; public Light(String location) { this.location = location; } public void on() { level = 100; System.out.println("Light is on"); } public void off() { level = 0; System.out.println("Light is off"); } public void dim(int level) { this.level = level; if (level == 0) { off(); } else { System.out.println("Light is dimmed to " + level + "%"); } } public int getLevel() { return level; } }
吊扇:
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; } }
遥控器客户端,测试遥控器:
public class RemoteLoader { public static void main(String[] args) { RemoteControlWithUndo remoteControl = new RemoteControlWithUndo(); Light livingRoomLight = new Light("Living Room"); LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight); LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight); remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff); remoteControl.onButtonWasPushed(0); remoteControl.offButtonWasPushed(0); System.out.println(remoteControl); remoteControl.undoButtonWasPushed(); remoteControl.offButtonWasPushed(0); remoteControl.onButtonWasPushed(0); System.out.println(remoteControl); remoteControl.undoButtonWasPushed(); 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(); } }
四、总结
Command模式的根本目的在于将“行为请求者”与“行为实现者”解耦,在面向对象语言中,常见的实现手段是“将行为抽象为对象”。
在被解耦的两者之间是通过命令对象进行沟通的。
命令对象封装了接收者和一个或一组动作。
调用者通过调用命令对象的execute()发出请求,这会使得接收者的动作被调用。
调用者可以接受命令当做参数,甚至在运行时动态地进行。
命令可以支持撤销,做法是实现一个undo()方法来回到execute()被执行前的状态。
应对软件变化