您的位置:首页 > 其它

《HeadFirst设计模式》读书笔记-第6章-命令模式

2017-03-21 23:06 323 查看

定义

命令模式(command pattern)将请求封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。



从上面的类图可以看出,一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接收者包装进对象中,这个对象只暴露出execute()/undo()方法,当execute()/undo()方法被调用的时候,接收者就会执行这些动作。站在客户的角度来看,它只要把接收者封装到命令对象,通过把命令对象设置到调用者,后续就由调用者去触发命令执行。把不同的命令对象作为参数传递给调用者,就会触发执行不同的命令。

代码实现

事实上每次接触到一个新的设计模式时,看到定义中的类图总觉得非常抽象,所以每次我都是先看具体的例子的代码实现,反过来再看定义中的类图的。例子的代码更加具体,直接,定义中的类图非常的抽象,通用,所以这两部分是我组织文章的重点。

废话不多说了,本例子要解决的问题是,遥控器的问题,遥控器有7个插槽,每个插槽有开/关两个按钮,每个插槽接上不同的家电就可以控制不同的家电,还有一个撤销按钮用来撤销上次的动作。由于不同的家电,给的控制API是不同的,该如何设计?



上面是类图,首先看Command接口的定义,它定义了2个方法,

public interface Command {
public void execute();
public void undo();
}


在看接收者(这里是不同的家电)给的API,

// 灯
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 LightOffCommand implements Command {
Light light;

public LightOffCommand(Light light) {
this.light = light;
}

public void execute() {
light.off();
}

public void undo() {
light.on();
}
}


// 开灯命令
public class LightOnCommand implements Command {
Light light;

public LightOnCommand(Light light) {
this.light = light;
}

public void execute() {
light.on();
}

public void undo() {
light.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 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 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 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();
}
}
}


定义空命令,也叫
null object
,用来初始化后面的调用者,这样可保证调用者已经持有了命令对象,要么是NoCommand,要么是具体的命令对象,可以避免去做是否为空的判断。

public class NoCommand implements Command {
public void execute() { }
public void undo() { }
}


调用者对象,

import java.util.*;

public class RemoteControlWithUndo {
Command[] onCommands;
Command[] offCommands;
Command undoCommand;

public RemoteControlWithUndo() {
// 对应7个开关按钮
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;
}

// 设置第slot个插槽的开/关命令对象
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}

public void onButtonWasPushed(int slot) {
// 这里可以避免去做onCommands[slot]是否为空的判断
onCommands[slot].execute();
undoCommand = onCommands[slot];
}

public void offButtonWasPushed(int slot) {
// 这里可以避免去做offCommands[slot]是否为空的判断
offCommands[slot].execute();
undoCommand = offCommands[slot];
}

public void undoButtonWasPushed() {
// 这里可以避免去做undoCommand是否为空的判断
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 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);
// 安装插槽0 命令对象
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);

// 触发插槽0对应的命令对象执行
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();
}
}


从上面的启动代码可以看出,给插槽安装不同的命令对象,就会触发不同的命令执行,而命令对象又有封装接收者及要执行的命令。这样遥控器就跟具体的家电解耦了,运行时安装不同了命令对象即可。

基于基本的命令模式,可做更多的扩展和应用:

实现宏命令

public class MacroCommand implements Command {
Command[] commands;

public MacroCommand(Command[] commands) {
this.commands = commands;
}
// 执行的是一组命令对象,而不是单个命令对象
public void execute() {
for (int i = 0; i < commands.length; i++) {
commands[i].execute();
}
}

public void undo() {
for (int i = 0; i < commands.length; i++) {
commands[i].undo();
}
}
}


这样在客户就可以自定义自己的命令组了,而且这个宏命令包含的命令组还是可以运行时动态调整的,非常灵活。

Command[] partyOn = { lightOn, stereoOn, tvOn, hottubOn};
Command[] partyOff = { lightOff, stereoOff, tvOff, hottubOff};

MacroCommand partyOnMacro = new MacroCommand(partyOn);
MacroCommand partyOffMacro = new MacroCommand(partyOff);

remoteControl.setCommand(0, partyOnMacro, partyOffMacro);
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);


实现队列请求

因为命令对象封装了一个接收者和一个/组动作,把命令对象作为参数传递,可以衍生出很多应用,如日程安排(scheduler),线程池(threadpool),工作队列(workqueue)等。

想象有一个工作队列,你在某一端添加命令,然后在另外一端则是线程。线程进行下面的动作,从队列中取出一个命令,调用它的execute()方法,执行完这个命令后丢弃,在取出下一个命令。

实现日志请求

某些应用需要我们将所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前的动作。通过新增两个方法(store(),load()),命令模式就能支持这一点,在系统正常执行命令时,将每个命令都存储在磁盘中,一旦系统死机重启后,将之前存储的命令load出来,重新调用execute()即可。

public interface Command {
public void execute();
public void undo();
public void store(Command[] cmd);
public void load();
}


包含的OO原则

原则2

RemoteControlWithUndo的onButtonWasPushed/offButtonWasPushed方法的实现就符合该原则, 面向接口编程。

本章金句

命令模式将发出请求的对象和执行请求的对象解耦。

在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接收者和一个或者一组动作。

实际操作时,很常见使用“聪明”命令对象,也就是命令对象直接实现了请求,而不是将工作委托给接收者。

命令也可以用来实现日志和事务系统。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息