Head First 设计模式(六)命令模式
来源:互联网 发布:天猫销售数据分析报告 编辑:程序博客网 时间:2024/06/05 00:43
1. 定义
命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作
这个模式我自己感觉理解不深,敲代码时没怎么遇见过这种设计模式。所以引用JAVA设计模式(15):行为型-命令模式(Command)中的一段话来进一步解释概念:
命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。
命令模式的核心在于引入了命令类,通过命令类来降低发送者和接收者的耦合度,请求发送者只需指定一个命令对象,再通过命令对象来调用请求接收者的处理方法
2. 类图
从类图中我们可以了解到,命令模式主要分为了四块:
- 命令(Command)
- 命令接受者(Receiver)
- 命令调用者(Invoker)
- 客户(Client)
整个流程可以看成,客户通过命令调用者来调用命令,而命令再调动命令接受者来处理。
如果不按照命令模式,那客户就要直接想办法调用Receiver的方法,耦合度大大增加。下面我们用场景代码来进一步说明
3. 场景+代码
场景:
现在我要设计一个遥控器类,上面有7对按钮,分别对应不同的功能(例如风扇开和风扇关、电灯开和电灯关),该如何设计?
我们先准备几个“功能类”,分别代码遥控器按钮要执行的功能:
/** * 电灯 */public class Light { public void on() { System.out.println("电灯打开了"); } public void off() { System.out.println("电灯关闭了"); }}/** * 车库门 */public class GarageDoor { public void on() { System.out.println("车库门打开了"); } public void off() { System.out.println("车库门关闭了"); }}
然后我们按最简单的逻辑,直接来设计这个遥控器:
public class OldRemoteControl { //遥控器控制的家居-电视 Light light; //遥控器控制的家居-车库门 GarageDoor garageDoor; …… public OldRemoteControl() { light = new Light(); garageDoor = new GarageDoor(); …… } /** * 按下某一开按钮 */ public void onButtonWasPressed(int i) { //假设遥控器第一个按钮控制电灯 if(i == 0){ light.on(); } //假设遥控器第二个按钮控制车库门 else if(i == 1){ garageDoor.on(); } …… } /** * 按下某一关按钮 */ public void offButtonWasPressed(int i) { //假设遥控器第一个按钮控制电灯 if(i == 0){ light.off(); } //假设遥控器第二个按钮控制车库门 else if(i == 1){ garageDoor.off(); } …… }}
这样写没什么问题。遥控器可以很正常的运行。
但是,我们很快发现:
- 每次遥控器按钮功能改变时,我们都需要改动代码,这违反了我们之前所说的“开闭设计原则”:类应该对拓展开发,对修改关闭。
- 遥控器和功能类的依赖太深,换句话说,这两个类耦合很严重。
如何改进?现在就是我们用命令模式的时刻啦~
代码
之前的定义讲过,命令模式的核心在于Command
类,“命令发送者”调用“命令类”,而“命令类”调用“命令接受者”,从而降低发送者和接收者的耦合度,接下来看它的详细实现:
command
/** * 命令接口 */public interface Command { /** * 执行命令 */ public void execute();}/** * 电灯打开命令类 */public class LightOnCommand implements Command{ /**receiver*/ private Light light; public LightOnCommand(Light light) { this.light = light; } public void execute(){ light.on(); }}/** * 电灯关闭命令类 */public class LightOffCommand implements Command{ private Light light; public LightOffCommand(Light light) { this.light = light; } public void execute(){ light.off(); }}/** * 车库门打开命令类 */public class GarageDoorOnCommand implements Command{ private GarageDoor garageDoor; public GarageDoorOnCommand(GarageDoor garageDoor) { this.garageDoor = garageDoor; } public void execute(){ garageDoor.on(); }}/** * 车库门关闭命令类 */public class GarageDoorOffCommand implements Command{ private GarageDoor garageDoor; public GarageDoorOffCommand(GarageDoor garageDoor) { this.garageDoor = garageDoor; } public void execute(){ garageDoor.off(); }}
receiver
/** * 电灯 */public class Light { public void on() { System.out.println("电灯打开了"); } public void off() { System.out.println("电灯关闭了"); }}/** * 车库门 */public class GarageDoor { public void on() { System.out.println("车库门打开了"); } public void off() { System.out.println("车库门关闭了"); }}
invoker
我们现在来看一下,加了Command
后的遥控器类:
/** * 遥控器,有多个指令 */public class RemoteControl { /**开命令按钮*/ Command[] onCommands; /**关命令按钮*/ Command[] offCommands; /**空对象*/ public static Command noCommand = new NoCommand(); public RemoteControl() { //设置遥控器有7对按钮,分别对应打开、关闭 onCommands = new Command[7]; offCommands = new Command[7]; //将所有按钮初始化为空按钮,反正空指针报错 for(int i = 0 ; i < 7 ; i++){ onCommands[i] = noCommand; offCommands[i] = noCommand; } } /** * 设置一对按钮 */ public void setCommand(int i,Command onCommand,Command offCommand) { onCommands[i] = onCommand; offCommands[i] = offCommand; } /** * 按下某一开按钮 */ public void onButtonWasPressed(int i) { onCommands[i].execute(); } /** * 按下某一关按钮 */ public void offButtonWasPressed(int i) { offCommands[i].execute(); }}/** * 一般invoker,只对应一个Command。此处给出作demo */public class SimpleRemoteControl { Command command; public void setCommand(Command command) { this.command = command; } public void buttonWasPressed() { command.execute(); }}
这段代码用到了“空对象”的概念。
我们想象一下,假如遥控器只设置了前两对按钮的功能,那为了我们按到其余按钮时程序不出错,需要进行判空操作:
public void onButtonWasPressed(int i) { if(onCommands[i] != null) onCommands[i].execute(); }
这样做麻烦很多,我们需要谨慎的判断各种可能出现空指针的情况,另外一旦出现空指针,程序都会被中断,而且没有任何空对象的提示。
为了改善这种情况,我们定义一个空对象命令类:
/** * 空指令,null的取代对象 */public class NoCommand implements Command{ @Override public void execute() { System.out.println("什么都不做……"); } @Override public void undo() { System.out.println("什么都不做……"); }}
再在命令调用类(遥控器)初始化时,指定所有按钮为空命令
public RemoteControl() { //设置遥控器有7对按钮,分别对应打开、关闭 onCommands = new Command[7]; offCommands = new Command[7]; //将所有按钮初始化为空按钮,反正空指针报错 for(int i = 0 ; i < 7 ; i++){ onCommands[i] = noCommand; offCommands[i] = noCommand; } }
这样,便不需要再进行空判断了,而且即使按错了空功能的按钮,也会进行提示。
client
最后,由客户,也就是遥控器操纵者来发送命令,进行测试:
/** * 遥控器客户 */public class RemoteControlClient { public static void main(String[] args) { //客户创建一组命令对象,并将其放入调用者中(想要的遥控器) RemoteControl remoteControl = generateControl(); //客户发送命令请求(操纵遥控器) remoteControl.onButtonWasPressed(0); System.out.println("================="); remoteControl.onButtonWasPressed(1); } /** * 创建一个遥控器 */ public static RemoteControl generateControl(){ Light light = new Light(); GarageDoor garageDoor = new GarageDoor(); Command lightOn = new LightOnCommand(light); Command lightOff = new LightOffCommand(light); Command garageDoorOn = new GarageDoorOnCommand(garageDoor); Command garageDoorOff = new GarageDoorOffCommand(garageDoor); RemoteControl remoteControl = new RemoteControl(); remoteControl.setCommand(0, lightOn, lightOff); remoteControl.setCommand(1, garageDoorOn,garageDoorOff); return remoteControl; }}
拓展
撤销功能
现在我想要遥控器有撤销功能,即按下撤销键,把最近的一次操作撤销掉,该如何做?
首先,给每个命令类都增加一个撤销的方法:
/** * 命令接口 */public interface Command { /** * 执行命令 */ public void execute(); /** * 撤销命令 */ public void undo();}/** * 电灯打开命令类 */public class LightOnCommand implements Command{ /**receiver*/ private Light light; public LightOnCommand(Light light) { this.light = light; } public void execute(){ light.on(); } @Override public void undo() { light.off(); }}/** * 电灯关闭命令类 */public class LightOffCommand implements Command{ private Light light; public LightOffCommand(Light light) { this.light = light; } public void execute(){ light.off(); } @Override public void undo() { light.on(); }}/** * 车库门打开命令类 */public class GarageDoorOnCommand implements Command{ private GarageDoor garageDoor; public GarageDoorOnCommand(GarageDoor garageDoor) { this.garageDoor = garageDoor; } public void execute(){ garageDoor.on(); } @Override public void undo() { garageDoor.off(); }}/** * 车库门关闭命令类 */public class GarageDoorOffCommand implements Command{ private GarageDoor garageDoor; public GarageDoorOffCommand(GarageDoor garageDoor) { this.garageDoor = garageDoor; } public void execute(){ garageDoor.off(); } @Override public void undo() { garageDoor.on(); }}
然后用一个变量,记录每次命令调用者(遥控器)的最后一次操作
public class RemoteControl { /**开命令按钮*/ Command[] onCommands; /**关命令按钮*/ Command[] offCommands; /**最后一次执行的命令*/ Command lastCommand; /**空对象*/ public static Command noCommand = new NoCommand(); public RemoteControl() { //设置遥控器有7对按钮,分别对应打开、关闭 onCommands = new Command[7]; offCommands = new Command[7]; //将所有按钮初始化为空按钮,反正空指针报错 for(int i = 0 ; i < 7 ; i++){ onCommands[i] = noCommand; offCommands[i] = noCommand; } lastCommand = noCommand; } /** * 设置一对按钮 */ public void setCommand(int i,Command onCommand,Command offCommand) { onCommands[i] = onCommand; offCommands[i] = offCommand; } /** * 按下某一开按钮 */ public void onButtonWasPressed(int i) { lastCommand = onCommands[i]; if(onCommands[i] != null) onCommands[i].execute(); } /** * 按下某一关按钮 */ public void offButtonWasPressed(int i) { lastCommand = offCommands[i]; offCommands[i].execute(); } /** * 撤销最近一次的操作 */ public void cancelButtonWasPressed() { lastCommand.undo(); }}
这样便成功了,如果想多次撤销之前的命令,同理,只需设置一个集合存储之前操作的所有命令,再一一调用undo()
方法即可。
多功能综合按钮
看到之前的代码,可能有人会疑惑“接受者”存在的意义。为什么不直接在命令类的execute()
方法中直接实现所有的逻辑呢?
之前设计的命令类,都是“简单式”的命令类。即它只懂得调用一个接受者的一个行为。当我们要实现“复杂式”的命令类,调用多个接受者行为时,“接受者”存在便很重要,它可以帮助我们解耦。看下面的例子。
现在我嫌遥控器按钮功能太单一,例如我想要按一个按钮同时开灯和开车库门,如何设计这种“批处理”按钮?
设计一个“批处理命令类”
/** * 复杂命令,由多个命令组成 */public class MacroCommand implements Command{ private Command[] commands; public MacroCommand(Command[] commands) { this.commands = commands; } @Override public void execute(){ for (Command command : commands) { command.execute(); } } @Override public void undo() { for (Command command : commands) { command.undo(); } }}
将批处理命令与按钮绑定,测试
/** * 遥控器客户 */public class RemoteControlClient { public static void main(String[] args) { //客户创建一组命令对象,并将其放入调用者中(想要的遥控器) RemoteControl remoteControl = generateControl(); //客户发送命令请求(操纵遥控器) System.out.println("========测试开灯========="); remoteControl.onButtonWasPressed(0); System.out.println("========测试开车库门========="); remoteControl.onButtonWasPressed(1); System.out.println("=========测试综合按钮========"); remoteControl.onButtonWasPressed(2); System.out.println("=========测试撤销按钮========"); remoteControl.cancelButtonWasPressed(); } /** * 创建一个遥控器 */ public static RemoteControl generateControl(){ Light light = new Light(); GarageDoor garageDoor = new GarageDoor(); //创建“傻瓜式”命令 Command lightOn = new LightOnCommand(light); Command lightOff = new LightOffCommand(light); Command garageDoorOn = new GarageDoorOnCommand(garageDoor); Command garageDoorOff = new GarageDoorOffCommand(garageDoor); //创建“批处理”命令 Command[] maxOn = new Command[]{lightOn,garageDoorOn}; Command[] maxOff = new Command[]{lightOff,garageDoorOff}; MacroCommand partyOn = new MacroCommand(maxOn); MacroCommand partyOff = new MacroCommand(maxOff); //将命令与调用者绑定 RemoteControl remoteControl = new RemoteControl(); remoteControl.setCommand(0, lightOn, lightOff); remoteControl.setCommand(1, garageDoorOn,garageDoorOff); remoteControl.setCommand(2, partyOn,partyOff); return remoteControl; }}/**Output:========测试开灯=========电灯打开了========测试开车库门=========车库门打开了=========测试综合按钮========电灯打开了车库门打开了=========测试撤销按钮========电灯关闭了车库门关闭了*/
3. 用途
命令模式运用于:线程池、工作队列和日志请求等等
本文总结自
《Head First 设计模式》第六章:命令模式
部分参考于:
JAVA设计模式(15):行为型-命令模式(Command)
- Head First 设计模式(六)命令模式
- 《Head First设计模式》要点(六)
- Head First 设计模式 (六) 命令模式(Command pattern) C++实现
- 《Head First 设计模式》阅读笔记(六)——命令模式
- Head First设计模式1 命令模式
- Head First设计模式-命令模式
- Head First设计模式 -- 命令模式
- 《Head First 设计模式》命令模式
- 《Head First 设计模式》之命令模式
- Head First设计模式-命令模式
- <Head First 设计模式>:命令模式:Command
- First Head-设计模式:命令模式
- Head First 设计模式之命令模式(CommandPattern)
- Head First设计模式学习笔记-------(6)命令模式
- Head First设计模式笔记(命令模式)
- Head First---命令模式
- Head First 命令模式
- Head First 设计模式
- Opencv3编程入门学习笔记(三)之访问图像像素的三种方法
- 进制转换
- 关于boa+cgi上传文件大小的问题
- JavaScript border与offsetWidth
- 九度OJ题目1107:搬水果
- Head First 设计模式(六)命令模式
- 喵哈哈村的木星传说(四)-(卢卡斯定理)
- Minimum Moves to Equal Array Elements
- 每天一个Linux命令(33):diff
- C#正则表达式的完全匹配、部分匹配及忽略大小写的问题
- 9.Django入门:高级教程-如何编写可重用的应用
- vector使用(STL)
- 计算机网络和因特网--分组交换网中的时延,丢包和吞吐量
- 找中位数