命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。
-
UML:
- 模型:家电自动遥控器
- 特点:我们想用一个遥控器来控制多个家电,例如,电灯,音响,空调,电视等。家电是请求的接收者,遥控器是请求的发送者,遥控器上有一些按钮,不同的按钮对应家电的不同操作。抽象命令角色由一个命令接口来扮演,有三个具体的命令类实现了抽象命令接口,这三个具体命令类分别代表三种操作:打开家电、关闭关闭和啥都不干。
public class Light {
//灯的位置,卧室,厨房...
String loc = "";
public Light(String loc) {
this.loc = loc;
}
public void On() {
System.out.println(loc + " On");
}
public void Off() {
System.out.println(loc + " Off");
}
}
//音响设备
public class Stereo {
static int volume = 0;
public void On() {
System.out.println("Stereo On");
}
public void Off() {
System.out.println("Stereo Off");
}
public void SetCd() {
System.out.println("Stereo SetCd");
}
public void SetVol(int vol) {
volume = vol;
System.out.println("Stereo volume=" + volume);
}
public int GetVol() {
return volume;
}
public void Start() {
System.out.println("Stereo Start");
}
}
//模拟设备厂家提供的遥控器控制接口
public interface Control {
public void onButton(int slot);
public void offButton(int slot);
public void undoButton();
}
使用传统的面向对象的设计,一般是创建一个遥控器实例,将灯、音响的实例传给遥控器,然后根据遥控器的按钮设置不同的家电操作,例如按下1号键是音响开,2号键是音响加音量等
public class TraditionControl implements Control {
Light light;
Stereo stereo;
public TraditionControl(Light light, Stereo stereo) {
this.light = light;
this.stereo = stereo;
}
@Override
public void onButton(int slot) {
// TODO Auto-generated method stub
switch (slot) {
case 0:
light.On();
break;
case 1:
stereo.On();
break;
case 2:
int vol = stereo.GetVol();
if (vol < 11) {
stereo.SetVol(++vol);
}
break;
}
}
@Override
public void offButton(int slot) {
// TODO Auto-generated method stub
switch (slot) {
case 0:
light.Off();
break;
case 1:
stereo.Off();
break;
case 2:
int vol = stereo.GetVol();
if (vol > 0) {
stereo.SetVol(--vol);
}
break;
}
}
@Override
public void undoButton() {
}
}
这样设计,如果要加一种设备,这种设计的控制器就不好添加了,首先要在控制器中注入设备,按键处理中要再加新设备的处理。所以,这样就高耦合了,不利于扩展。
- 使用命令模式:
//将命令抽象处理请求接口
public interface Command {
public void execute();
public void undo();
}
//卧室关灯命令
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light){
this.light=light;
}
@Override
public void execute() {
light.Off();
}
@Override
public void undo() {
light.On();
}
}
//卧室开灯命令
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light){
this.light=light;
}
@Override
public void execute() {
light.On();
}
@Override
public void undo() {
light.Off();
}
}
//操作无响应命令
public class NoCommand implements Command {
@Override
public void execute() {}
@Override
public void undo() {}
}
//音响开命令
public class StereoOnCommand implements Command {
private Stereo setreo;
public StereoOnCommand(Stereo setreo){
this.setreo=setreo;
}
@Override
public void execute() {
setreo.On();
setreo.SetCd();
}
@Override
public void undo() {
setreo.Off();
}
}
//音响关命令
public class StereoOffCommand implements Command {
private Stereo setreo;
public StereoOffCommand(Stereo setreo){
this.setreo=setreo;
}
@Override
public void execute() {
setreo.Off();
}
@Override
public void undo() {
setreo.On();
setreo.SetCd();
}
}
//音响加音量
public class StereoAddVolCommand implements Command{
private Stereo setreo;
public StereoAddVolCommand(Stereo setreo){
this.setreo=setreo;
}
@Override
public void execute() {
int vol= setreo.GetVol();
if(vol<11){
setreo.SetVol(++vol);
}
}
@Override
public void undo() {
int vol= setreo.GetVol();
if(vol>0){
setreo.SetVol(--vol);
}
}
}
//音响减音量命令
public class StereoSubVolCommand implements Command{
private Stereo setreo;
public StereoSubVolCommand(Stereo setreo){
this.setreo=setreo;
}
@Override
public void execute() {
int vol= setreo.GetVol();
if(vol>0){
setreo.SetVol(--vol);
}
}
@Override
public void undo() {
int vol= setreo.GetVol();
if(vol<11){
setreo.SetVol(++vol);
}
}
}
//当有了这样命令之后,我们只需要将命令设置给遥控器的不同的按键即可,用户在按键时会请求执行已经设定好的命令,由命令再调用具体家电执行。
public class CommandModeControl implements Control{
//所有开命令
private Command[] onCommands;
//所有关命令
private Command[] offCommands;
//记录上一个执行的命令
private Stack<Command> stack=new Stack<Command>();
public CommandModeControl(){
onCommands=new Command[5];
offCommands=new Command[5];
Command noCommand=new NoCommand();
// 初始化为无操作命令
for(int i=0,len=onCommands.length;i<len;i++) {
onCommands[i]=noCommand;
offCommands[i]=noCommand;
}
}
//设置具体按键命令
public void setCommand(int slot,Command onCommand,Command offCommand){
onCommands[slot]=onCommand;
offCommands[slot]=offCommand;
}
@Override
public void onButton(int slot) {
//执行命令
onCommands[slot].execute();
stack.push(onCommands[slot]);
}
@Override
public void offButton(int slot) {
offCommands[slot].execute();
stack.push(offCommands[slot]);
}
@Override
public void undoButton() {
stack.pop().undo();
}
}
//使用遥控器
public class ControlTest {
public static void main(String[] args) {
//创建遥控器,也就是UML中的Invoker实现类
CommandModeControl control = new CommandModeControl();
Light bedroomlight = new Light("BedRoom");
Light kitchlight = new Light("Kitch");
Stereo stereo = new Stereo();
//设置各种命令
LightOnCommand bedroomlighton = new LightOnCommand(bedroomlight);
LightOffCommand bedroomlightoff = new LightOffCommand(bedroomlight);
LightOnCommand kitchlighton = new LightOnCommand(kitchlight);
LightOffCommand kitchlightoff = new LightOffCommand(kitchlight);
Command[] oncommands={bedroomlighton,kitchlighton};
Command[] offcommands={bedroomlightoff,kitchlightoff};
StereoOnCommand stereoOn = new StereoOnCommand(stereo);
StereoOffCommand stereoOff = new StereoOffCommand(stereo);
StereoAddVolCommand stereoaddvol = new StereoAddVolCommand(stereo);
StereoSubVolCommand stereosubvol = new StereoSubVolCommand(stereo);
//将命令注入到遥控器中
control.setCommand(0, bedroomlighton, bedroomlightoff);
control.setCommand(1, kitchlighton, kitchlightoff);
control.setCommand(2, stereoOn, stereoOff);
control.setCommand(3, stereoaddvol, stereosubvol);
//操作遥控器就能执行已经绑定好的命令控制家电了
control.onButton(0);
control.undoButton();
//control.offButton(0);
control.onButton(1);
control.offButton(1);
control.onButton(2);
control.onButton(3);
control.offButton(3);
control.undoButton();
control.offButton(2);
control.undoButton();
}
}
通过上面的设计,遥控器不需要注入家电设备,而是注入各种命令。如果新加一种家电,则只需要扩展新的命令,并注入到遥控器中即可。遥控器不关心家电的实现,它是调用命令接口,解耦了遥控器与家电。但是可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类。