《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原则
原则2RemoteControlWithUndo的onButtonWasPushed/offButtonWasPushed方法的实现就符合该原则, 面向接口编程。
本章金句
命令模式将发出请求的对象和执行请求的对象解耦。在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接收者和一个或者一组动作。
实际操作时,很常见使用“聪明”命令对象,也就是命令对象直接实现了请求,而不是将工作委托给接收者。
命令也可以用来实现日志和事务系统。
相关文章推荐
- HeadFirst设计模式_读书笔记_005_命令模式
- 【读书笔记】HeadFirst设计模式——命令模式简述
- Delphi 设计模式:《HeadFirst设计模式》Delphi7代码---命令模式之SimpleRemoteControlTest
- 命令模式 Command – 学习HeadFirst设计模式记录
- HeadFirst设计模式之命令模式
- 《Android源码设计模式》读书笔记 (11) 第11章 命令模式
- Delphi 设计模式:《HeadFirst设计模式》Delphi7代码---命令模式之RemoteControlTest
- 【设计模式】《Head First 设计模式》读书笔记——命令模式
- 《Head First Design Patterns》读书笔记之命令模式
- 读书笔记之 - javascript 设计模式 - 命令模式
- 设计模式读书笔记-----命令模式
- 读书笔记18:命令模式
- 【读书笔记】HeadFirst设计模式——单件不简单:详述实现Singleton模式需要考虑的方方面面
- 读书笔记_java设计模式深入研究 第十章 命令模式 Command
- 设计模式读书笔记之命令模式(Command Pattern)
- 第二十三章烤羊肉串引来的思考--命令模式(读书笔记)
- Head First 设计模式 第6章 命令模式
- HeadFirst设计模式(六) - 命令模式