互动网站欣赏,柳州seo培训,长沙公众号开发,wordpress add page声明#xff1a;仅为个人学习总结#xff0c;还请批判性查看#xff0c;如有不同观点#xff0c;欢迎交流。
摘要
《Head First设计模式》第6章笔记#xff1a;结合示例应用和代码#xff0c;介绍命令模式#xff0c;包括遇到的问题、采用的解决方案、遵循的 OO 原则、…声明仅为个人学习总结还请批判性查看如有不同观点欢迎交流。
摘要
《Head First设计模式》第6章笔记结合示例应用和代码介绍命令模式包括遇到的问题、采用的解决方案、遵循的 OO 原则、以及达到的效果。 目录 摘要1 示例应用2 遇到问题3 引入设计模式3.1 改进设计3.1.1 封装变化3.1.2 针对接口编程3.1.3 新版 API 设计3.1.4 API 应用示例3.1.5 系统类图 3.2 实现撤销3.3 组合命令3.4 命令模式定义 4 示例代码4.1 Java 示例4.2 C11 示例 5 设计工具箱5.1 OO 基础5.2 OO 原则5.3 OO 模式 参考 1 示例应用
本章的示例是为家居自动化遥控器设计编程接口API。
遥控器有着众多款式。 它们的共同点是
表面分布着各种按钮用于控制各种家居自动化设备包含一个全局撤销按钮用于取消最近一次按钮操作的效果。
家居自动化设备包括电灯、风扇、音响、洒水器等等它们由多家厂商提供每种设备都有对应的 Java 类用于进行自动化控制。 现有设备类的接口如下接口各异并没有统一的定义 #mermaid-svg-KAYQexOKBEDdh5Af {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-KAYQexOKBEDdh5Af .error-icon{fill:#552222;}#mermaid-svg-KAYQexOKBEDdh5Af .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KAYQexOKBEDdh5Af .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-KAYQexOKBEDdh5Af .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KAYQexOKBEDdh5Af .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KAYQexOKBEDdh5Af .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KAYQexOKBEDdh5Af .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KAYQexOKBEDdh5Af .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KAYQexOKBEDdh5Af .marker.cross{stroke:#333333;}#mermaid-svg-KAYQexOKBEDdh5Af svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KAYQexOKBEDdh5Af g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-KAYQexOKBEDdh5Af g.classGroup text .title{font-weight:bolder;}#mermaid-svg-KAYQexOKBEDdh5Af .nodeLabel,#mermaid-svg-KAYQexOKBEDdh5Af .edgeLabel{color:#131300;}#mermaid-svg-KAYQexOKBEDdh5Af .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-KAYQexOKBEDdh5Af .label text{fill:#131300;}#mermaid-svg-KAYQexOKBEDdh5Af .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-KAYQexOKBEDdh5Af .classTitle{font-weight:bolder;}#mermaid-svg-KAYQexOKBEDdh5Af .node rect,#mermaid-svg-KAYQexOKBEDdh5Af .node circle,#mermaid-svg-KAYQexOKBEDdh5Af .node ellipse,#mermaid-svg-KAYQexOKBEDdh5Af .node polygon,#mermaid-svg-KAYQexOKBEDdh5Af .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KAYQexOKBEDdh5Af .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-KAYQexOKBEDdh5Af g.clickable{cursor:pointer;}#mermaid-svg-KAYQexOKBEDdh5Af g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-KAYQexOKBEDdh5Af g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-KAYQexOKBEDdh5Af .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-KAYQexOKBEDdh5Af .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-KAYQexOKBEDdh5Af .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-KAYQexOKBEDdh5Af .dashed-line{stroke-dasharray:3;}#mermaid-svg-KAYQexOKBEDdh5Af #compositionStart,#mermaid-svg-KAYQexOKBEDdh5Af .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KAYQexOKBEDdh5Af #compositionEnd,#mermaid-svg-KAYQexOKBEDdh5Af .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KAYQexOKBEDdh5Af #dependencyStart,#mermaid-svg-KAYQexOKBEDdh5Af .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KAYQexOKBEDdh5Af #dependencyStart,#mermaid-svg-KAYQexOKBEDdh5Af .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KAYQexOKBEDdh5Af #extensionStart,#mermaid-svg-KAYQexOKBEDdh5Af .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KAYQexOKBEDdh5Af #extensionEnd,#mermaid-svg-KAYQexOKBEDdh5Af .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KAYQexOKBEDdh5Af #aggregationStart,#mermaid-svg-KAYQexOKBEDdh5Af .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KAYQexOKBEDdh5Af #aggregationEnd,#mermaid-svg-KAYQexOKBEDdh5Af .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KAYQexOKBEDdh5Af .edgeTerminals{font-size:11px;}#mermaid-svg-KAYQexOKBEDdh5Af :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Light on() off() CeilingLight on() off() dim() CeilingFan high() low() off() getSpeed() TV on() off() setChannel() setVolume() GardenLight setDuskTime() setDawnTime() manualOn() manualOff() Stereo on() off() setCd() setDvd() setRadio() setVolume() Hottub jetsOn() jetsOff() circulate() setTemperature() GarageDoor up() down() stop() lightOn() lightOff() Sprinkler waterOn() waterOff() SecurityControl arm() disarm() 现在需要为遥控器创建一套 API
对于遥控器的开发者在开发过程中能够使用 API 配置遥控器的控制功能对于遥控器系统在运行过程中能够按照开发者的配置执行控制操作。
具体要求如下 将每个按钮与一个或一组家居设备绑定通过按钮控制相应设备实现特定的功能。如下表所示 按钮编号绑定设备控制功能功能类型1号客厅灯开灯① 单一设备、单一操作2号客厅灯关灯① 单一设备、单一操作5号所有室内灯关灯② 多个设备、组合操作6号风扇高风速① 单一设备、单一操作9号电视打开并设置1频道③ 单一设备、多项操作 通过撤销按钮可以取消最近一次按钮操作不包括撤销操作的效果恢复到操作前的状态 能够支持现有的全部设备以及厂商未来可能提供的任何设备。
遥控器 RemoteControl 类的框架如下只包含3个方法签名其中的 TODO 是 API 设计需要完成的内容。
public class RemoteControl {// TODO: 定义 API 接口将“遥控器上的按钮”与“自动化设备的控制功能”绑定// 补充必要的变量和方法/*** 构造方法在系统启动时由硬件层触发调用* param buttonCount 遥控器按钮数量不包括撤销按钮*/RemoteControl(int buttonCount) {// TODO: 实现必要的初始化操作}/*** 按钮“按下事件”的处理方法当按钮被按下时由硬件层触发调用* 用于控制与按钮绑定的自动化设备实现特定的功能* param buttonIndex 被按下按钮的索引从 0 开始*/void buttonWasPushed(int buttonIndex) {// TODO: 实现控制功能}/*** 撤销按钮“按下事件”的处理方法当撤销按钮被按下时由硬件层触发调用* 用于撤销最近一次按钮操作不包括撤销操作的效果*/void undoButtonWasPushed() {// TODO: 实现撤销功能}
}2 遇到问题
在明确需求后首先针对最基础的“单一设备”控制功能我们有了第1版的 API 设计
public class RemoteControl {// 保存每个按钮对应的设备和操作private Object[] devices;private Integer[] operations;RemoteControl(int buttonCount) {devices new Object[buttonCount];operations new Integer[buttonCount];}// API 接口设置按钮对应的设备和操作public void setButtonOperation(int buttonIndex, Object device, int operation) {devices[buttonIndex] device;operations[buttonIndex] operation;}// 根据按钮索引找到对应的设备并对其进行控制void buttonWasPushed(int buttonIndex) {Object device devices[buttonIndex];int operation operations[buttonIndex];if (device instanceof Light) {Light light (Light) device; // 电灯if (operation 1) { light.on(); } // 开灯else if (operation 0) { light.off(); } // 关灯} else if (device instanceof CeilingFan) {CeilingFan ceilingFan (CeilingFan) device; // 风扇if (operation CeilingFan.HIGH) { ceilingFan.high(); } // 高速else if (operation CeilingFan.LOW) { ceilingFan.low(); } // 低速else if (operation CeilingFan.OFF) { ceilingFan.off(); } // 关闭}// 其它设备和操作略}void undoButtonWasPushed() {// TODO: 实现撤销功能}
}思考题
当前的 API 设计违反了下面哪些设计原则多选【参考答案在第 20 行】下文 “5.2.2 原则回顾” 部分有详细些的原则介绍A. 分离变与不变Identify the aspects of your application that vary and separate them from what stays the same.
B. 针对接口编程Program to interfaces, not implementations.
C. 优先使用组合Favor composition over inheritance.
D. 松耦合设计Strive for loosely coupled designs between objects that interact.
E. 开闭原则Classes should be open for extension, but closed for modification.
F. 依赖倒置Depend on abstractions. Do not depend on concrete classes.参考答案A B D E F
参考解析A. 分离变与不变问题在 buttonWasPushed() 方法中设备的类型、执行的操作是变化的方面改进将变化的方面提取出来进行封装。B. 针对接口编程问题针对 Light、CeilingFan 等具体类编程而不是针对它们共同的接口改进定义抽象接口针对接口编程。D. 松耦合设计问题RemoteControl 类与所有设备类强耦合改进通过接口降低耦合度。E. 开闭原则问题RemoteControl 类没有对修改关闭每当增加新的设备类型时都需要修改 buttonWasPushed() 方法改进访问所有设备类型共同的接口这样在扩展新设备类型时就不需要修改 RemoteControl 类。F. 依赖倒置问题RemoteControl 类高层组件依赖具体设备类低层组件而不是“抽象”改进定义抽象接口使 RemoteControl 类依赖于抽象。综上当前设计的改进方向为封装变化分离变与不变、针对接口编程。3 引入设计模式 3.1 改进设计
参照 OO 设计原则我们来尝试改进现有 API 的设计。 3.1.1 封装变化
在 buttonWasPushed() 方法中变化的方面包括
设备对象的类型例如 Light、CeilingFan、TV 等等设备对象的操作例如 on()、high()、setChannel() 等等
需要将这些变化的方面提取出来进行封装。
例如将操作 on() 和执行操作的设备对象 light 提取出来封装在一起。 由于封装后的功能是向 light 发送 on() 请求或命令所以将封装后的类命名为 LightOnCommand
public class LightOnCommand {// 将 light 对象封装在命令类内部private Light light;// 通过构造方法设置 light 对象作为命令的执行者/接收者Receiverpublic LightOnCommand(Light light) {this.light light;}// 将 on() 封装在命令方法中每当执行命令时就请求 light 对象执行 on() 操作public void execute() {light.on();}
}类似的将 high() 和 ceilingFan 封装在一起可以得到 CeilingFanHighCommand 类
public class CeilingFanHighCommand {private CeilingFan ceilingFan;public CeilingFanHighCommand(CeilingFan ceilingFan) {this.ceilingFan ceilingFan;}public void execute() {ceilingFan.high();}
}对于需要执行“多项操作”的命令同样可以进行封装例如 TVOnCommand 类
public class TVOnCommand {private TV tv;public TVOnCommand(TV tv) {this.tv tv;}// 在 execute() 方法中执行了一组操作public void execute() {tv.on();tv.setChannel(1);tv.setVolume(11);}
}3.1.2 针对接口编程
接下来遵循“针对接口编程”原则为所有命令定义统一的 Command 接口
public interface Command {// 声明 execute() 方法用于执行命令public void execute();
}每个具体命令都需要实现 Command 接口
public class LightOnCommand implements Command { /* ... */ }
public class CeilingFanHighCommand implements Command { /* ... */ }
public class TVOnCommand implements Command { /* ... */ }3.1.3 新版 API 设计
遵循“封装变化”、“针对接口编程”原则改进后的 API 设计如下
public class RemoteControl {// 保存每个按钮对应的命令private Command[] commands;RemoteControl(int buttonCount) {commands new Command[buttonCount];}// API 接口设置按钮对应的命令public void setCommand(int buttonIndex, Command command) {commands[buttonIndex] command;}// 直接执行命令void buttonWasPushed(int buttonIndex) {commands[buttonIndex].execute();}void undoButtonWasPushed() {// TODO: 实现撤销功能}
}现在RemoteControl 作为命令的调用者Invoker
只需要按照 Command 接口的定义统一调用 execute() 方法执行命令不再需要了解具体的设备类型、设备操作无论是增加新的设备类型还是支持新的设备操作都不需要修改 RemoteControl 类。 3.1.4 API 应用示例
遥控器 API 的应用示例如下
public class RemoteLoader {public static void main(String args[]) {RemoteControl remoteControl new RemoteControl(10);// 创建命令对象Light livingRoomLight new Light(Living Room);LightOnCommand livingRoomLightOn new LightOnCommand(livingRoomLight);LightOffCommand livingRoomLightOff new LightOffCommand(livingRoomLight);// 将遥控器按钮与命令对象绑定remoteControl.setCommand(0, livingRoomLightOn);remoteControl.setCommand(1, livingRoomLightOff);// 在按钮被按下时执行相应的命令remoteControl.buttonWasPushed(0);remoteControl.buttonWasPushed(1);}
}3.1.5 系统类图 #mermaid-svg-NWgRtp18Kvf7LeFC {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-NWgRtp18Kvf7LeFC .error-icon{fill:#552222;}#mermaid-svg-NWgRtp18Kvf7LeFC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NWgRtp18Kvf7LeFC .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-NWgRtp18Kvf7LeFC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NWgRtp18Kvf7LeFC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NWgRtp18Kvf7LeFC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NWgRtp18Kvf7LeFC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NWgRtp18Kvf7LeFC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NWgRtp18Kvf7LeFC .marker.cross{stroke:#333333;}#mermaid-svg-NWgRtp18Kvf7LeFC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NWgRtp18Kvf7LeFC g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-NWgRtp18Kvf7LeFC g.classGroup text .title{font-weight:bolder;}#mermaid-svg-NWgRtp18Kvf7LeFC .nodeLabel,#mermaid-svg-NWgRtp18Kvf7LeFC .edgeLabel{color:#131300;}#mermaid-svg-NWgRtp18Kvf7LeFC .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-NWgRtp18Kvf7LeFC .label text{fill:#131300;}#mermaid-svg-NWgRtp18Kvf7LeFC .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-NWgRtp18Kvf7LeFC .classTitle{font-weight:bolder;}#mermaid-svg-NWgRtp18Kvf7LeFC .node rect,#mermaid-svg-NWgRtp18Kvf7LeFC .node circle,#mermaid-svg-NWgRtp18Kvf7LeFC .node ellipse,#mermaid-svg-NWgRtp18Kvf7LeFC .node polygon,#mermaid-svg-NWgRtp18Kvf7LeFC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NWgRtp18Kvf7LeFC .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-NWgRtp18Kvf7LeFC g.clickable{cursor:pointer;}#mermaid-svg-NWgRtp18Kvf7LeFC g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-NWgRtp18Kvf7LeFC g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-NWgRtp18Kvf7LeFC .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-NWgRtp18Kvf7LeFC .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-NWgRtp18Kvf7LeFC .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-NWgRtp18Kvf7LeFC .dashed-line{stroke-dasharray:3;}#mermaid-svg-NWgRtp18Kvf7LeFC #compositionStart,#mermaid-svg-NWgRtp18Kvf7LeFC .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NWgRtp18Kvf7LeFC #compositionEnd,#mermaid-svg-NWgRtp18Kvf7LeFC .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NWgRtp18Kvf7LeFC #dependencyStart,#mermaid-svg-NWgRtp18Kvf7LeFC .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NWgRtp18Kvf7LeFC #dependencyStart,#mermaid-svg-NWgRtp18Kvf7LeFC .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NWgRtp18Kvf7LeFC #extensionStart,#mermaid-svg-NWgRtp18Kvf7LeFC .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NWgRtp18Kvf7LeFC #extensionEnd,#mermaid-svg-NWgRtp18Kvf7LeFC .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NWgRtp18Kvf7LeFC #aggregationStart,#mermaid-svg-NWgRtp18Kvf7LeFC .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NWgRtp18Kvf7LeFC #aggregationEnd,#mermaid-svg-NWgRtp18Kvf7LeFC .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NWgRtp18Kvf7LeFC .edgeTerminals{font-size:11px;}#mermaid-svg-NWgRtp18Kvf7LeFC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} «interface» Command execute() LightOnCommand -Light light execute() : CallOnOfLight LightOffCommand -Light light execute() : CallOffOfLight Light on() off() RemoteLoader RemoteControl -Command[] commands setCommand() -buttonWasPushed() : CallExecuteOfCommand LightOnCommand 和 LightOffCommand 实现 Command 接口通过封装 Light 对象及其操作来执行请求。 注由于当前 mermaid 类图不支持 note所以方法method的返回类型都被用于作为注释如 CallOnOfLight RemoteControl命令调用者 通过 Command 对象提交请求与 Light命令接收者解耦。 RemoteLoader 创建命令对象并将其设置给命令调用者。 3.2 实现撤销
撤销的核心思想是每个命令都需要能够“撤销”自己的执行效果。 撤销的实现方式为
在 Command 接口中声明抽象方法 undo()由具体命令实现 undo() 方法 对于无状态撤销: 直接执行反向操作如开灯-关灯对于有状态撤销: 恢复之前保存的状态如风扇速度 保存最近一次执行的命令在需要撤销时调用命令的 undo() 方法。 3.2.1 声明 undo() 方法 #mermaid-svg-LjlYg3Yg26tdDjGg {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-LjlYg3Yg26tdDjGg .error-icon{fill:#552222;}#mermaid-svg-LjlYg3Yg26tdDjGg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LjlYg3Yg26tdDjGg .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-LjlYg3Yg26tdDjGg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LjlYg3Yg26tdDjGg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LjlYg3Yg26tdDjGg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LjlYg3Yg26tdDjGg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LjlYg3Yg26tdDjGg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LjlYg3Yg26tdDjGg .marker.cross{stroke:#333333;}#mermaid-svg-LjlYg3Yg26tdDjGg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LjlYg3Yg26tdDjGg g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-LjlYg3Yg26tdDjGg g.classGroup text .title{font-weight:bolder;}#mermaid-svg-LjlYg3Yg26tdDjGg .nodeLabel,#mermaid-svg-LjlYg3Yg26tdDjGg .edgeLabel{color:#131300;}#mermaid-svg-LjlYg3Yg26tdDjGg .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-LjlYg3Yg26tdDjGg .label text{fill:#131300;}#mermaid-svg-LjlYg3Yg26tdDjGg .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-LjlYg3Yg26tdDjGg .classTitle{font-weight:bolder;}#mermaid-svg-LjlYg3Yg26tdDjGg .node rect,#mermaid-svg-LjlYg3Yg26tdDjGg .node circle,#mermaid-svg-LjlYg3Yg26tdDjGg .node ellipse,#mermaid-svg-LjlYg3Yg26tdDjGg .node polygon,#mermaid-svg-LjlYg3Yg26tdDjGg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LjlYg3Yg26tdDjGg .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-LjlYg3Yg26tdDjGg g.clickable{cursor:pointer;}#mermaid-svg-LjlYg3Yg26tdDjGg g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-LjlYg3Yg26tdDjGg g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-LjlYg3Yg26tdDjGg .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-LjlYg3Yg26tdDjGg .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-LjlYg3Yg26tdDjGg .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-LjlYg3Yg26tdDjGg .dashed-line{stroke-dasharray:3;}#mermaid-svg-LjlYg3Yg26tdDjGg #compositionStart,#mermaid-svg-LjlYg3Yg26tdDjGg .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-LjlYg3Yg26tdDjGg #compositionEnd,#mermaid-svg-LjlYg3Yg26tdDjGg .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-LjlYg3Yg26tdDjGg #dependencyStart,#mermaid-svg-LjlYg3Yg26tdDjGg .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-LjlYg3Yg26tdDjGg #dependencyStart,#mermaid-svg-LjlYg3Yg26tdDjGg .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-LjlYg3Yg26tdDjGg #extensionStart,#mermaid-svg-LjlYg3Yg26tdDjGg .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-LjlYg3Yg26tdDjGg #extensionEnd,#mermaid-svg-LjlYg3Yg26tdDjGg .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-LjlYg3Yg26tdDjGg #aggregationStart,#mermaid-svg-LjlYg3Yg26tdDjGg .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-LjlYg3Yg26tdDjGg #aggregationEnd,#mermaid-svg-LjlYg3Yg26tdDjGg .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-LjlYg3Yg26tdDjGg .edgeTerminals{font-size:11px;}#mermaid-svg-LjlYg3Yg26tdDjGg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} «interface» Command execute() undo() 3.2.2 实现 undo() 方法无状态
以 LightOnCommand 为例执行 execute() 开灯后如果撤销命令对应的就是执行关灯操作。
public class LightOnCommand implements Command {private Light light;public LightOnCommand(Light light) { this.light light; }public void execute() { light.on(); }// 直接执行反向操作public void undo() { light.off(); }
}3.2.3 实现 undo() 方法有状态
以 CeilingFanOffCommand 为例由于风扇具有多种状态所以在执行 execute() 关闭风扇之前需要保存风扇当前的状态以便在撤销命令时可以恢复到之前保存的状态。
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.LOW) { ceilingFan.low(); }else if (prevSpeed CeilingFan.OFF) { ceilingFan.off(); }}
}3.2.4 调用 undo() 方法
为了能够撤销最近一次执行的命令需要先保存该命令在执行撤销时通过调用该命令的 undo() 方法将系统恢复到命令执行前的状态。
public class RemoteControl {private Command[] commands;private Command undoCommand;void buttonWasPushed(int buttonIndex) {commands[buttonIndex].execute();undoCommand commands[buttonIndex]; // 保存最近一次执行的命令}void undoButtonWasPushed() {// 调用最近一次执行命令的 undo() 方法恢复到命令执行前的状态undoCommand.undo();}// 其它变量和方法略
}如果需要撤销最近执行的多个命令而不仅仅是一个命令 可以将已经执行的命令保存在一个栈历史表列中每当需要撤销命令时就弹出栈顶命令调用其 undo() 方法。 3.2.5 记录命令日志
与撤销功能类似通过在命令中定义 store() 和 load() 方法还可以将命令写到日志中并在需要的时候重新加载和执行命令。 这项功能对于当前的遥控器来说并没有意义但是在一些数据处理应用中通过记录日志可以在异常发生后实现数据恢复。 #mermaid-svg-l5Rh8jGSuuddrRZR {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-l5Rh8jGSuuddrRZR .error-icon{fill:#552222;}#mermaid-svg-l5Rh8jGSuuddrRZR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-l5Rh8jGSuuddrRZR .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-l5Rh8jGSuuddrRZR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-l5Rh8jGSuuddrRZR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-l5Rh8jGSuuddrRZR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-l5Rh8jGSuuddrRZR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-l5Rh8jGSuuddrRZR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-l5Rh8jGSuuddrRZR .marker.cross{stroke:#333333;}#mermaid-svg-l5Rh8jGSuuddrRZR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-l5Rh8jGSuuddrRZR g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-l5Rh8jGSuuddrRZR g.classGroup text .title{font-weight:bolder;}#mermaid-svg-l5Rh8jGSuuddrRZR .nodeLabel,#mermaid-svg-l5Rh8jGSuuddrRZR .edgeLabel{color:#131300;}#mermaid-svg-l5Rh8jGSuuddrRZR .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-l5Rh8jGSuuddrRZR .label text{fill:#131300;}#mermaid-svg-l5Rh8jGSuuddrRZR .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-l5Rh8jGSuuddrRZR .classTitle{font-weight:bolder;}#mermaid-svg-l5Rh8jGSuuddrRZR .node rect,#mermaid-svg-l5Rh8jGSuuddrRZR .node circle,#mermaid-svg-l5Rh8jGSuuddrRZR .node ellipse,#mermaid-svg-l5Rh8jGSuuddrRZR .node polygon,#mermaid-svg-l5Rh8jGSuuddrRZR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-l5Rh8jGSuuddrRZR .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-l5Rh8jGSuuddrRZR g.clickable{cursor:pointer;}#mermaid-svg-l5Rh8jGSuuddrRZR g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-l5Rh8jGSuuddrRZR g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-l5Rh8jGSuuddrRZR .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-l5Rh8jGSuuddrRZR .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-l5Rh8jGSuuddrRZR .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-l5Rh8jGSuuddrRZR .dashed-line{stroke-dasharray:3;}#mermaid-svg-l5Rh8jGSuuddrRZR #compositionStart,#mermaid-svg-l5Rh8jGSuuddrRZR .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-l5Rh8jGSuuddrRZR #compositionEnd,#mermaid-svg-l5Rh8jGSuuddrRZR .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-l5Rh8jGSuuddrRZR #dependencyStart,#mermaid-svg-l5Rh8jGSuuddrRZR .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-l5Rh8jGSuuddrRZR #dependencyStart,#mermaid-svg-l5Rh8jGSuuddrRZR .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-l5Rh8jGSuuddrRZR #extensionStart,#mermaid-svg-l5Rh8jGSuuddrRZR .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-l5Rh8jGSuuddrRZR #extensionEnd,#mermaid-svg-l5Rh8jGSuuddrRZR .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-l5Rh8jGSuuddrRZR #aggregationStart,#mermaid-svg-l5Rh8jGSuuddrRZR .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-l5Rh8jGSuuddrRZR #aggregationEnd,#mermaid-svg-l5Rh8jGSuuddrRZR .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-l5Rh8jGSuuddrRZR .edgeTerminals{font-size:11px;}#mermaid-svg-l5Rh8jGSuuddrRZR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} «interface» Command execute() undo() store() load() 3.3 组合命令
为了实现“使用一个按钮关闭所有室内灯”我们需要引入一个新的命令类型 MacroCommand #mermaid-svg-SYOKcYGkn5tGFMj2 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SYOKcYGkn5tGFMj2 .error-icon{fill:#552222;}#mermaid-svg-SYOKcYGkn5tGFMj2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SYOKcYGkn5tGFMj2 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-SYOKcYGkn5tGFMj2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SYOKcYGkn5tGFMj2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SYOKcYGkn5tGFMj2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SYOKcYGkn5tGFMj2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SYOKcYGkn5tGFMj2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SYOKcYGkn5tGFMj2 .marker.cross{stroke:#333333;}#mermaid-svg-SYOKcYGkn5tGFMj2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SYOKcYGkn5tGFMj2 g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-SYOKcYGkn5tGFMj2 g.classGroup text .title{font-weight:bolder;}#mermaid-svg-SYOKcYGkn5tGFMj2 .nodeLabel,#mermaid-svg-SYOKcYGkn5tGFMj2 .edgeLabel{color:#131300;}#mermaid-svg-SYOKcYGkn5tGFMj2 .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-SYOKcYGkn5tGFMj2 .label text{fill:#131300;}#mermaid-svg-SYOKcYGkn5tGFMj2 .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-SYOKcYGkn5tGFMj2 .classTitle{font-weight:bolder;}#mermaid-svg-SYOKcYGkn5tGFMj2 .node rect,#mermaid-svg-SYOKcYGkn5tGFMj2 .node circle,#mermaid-svg-SYOKcYGkn5tGFMj2 .node ellipse,#mermaid-svg-SYOKcYGkn5tGFMj2 .node polygon,#mermaid-svg-SYOKcYGkn5tGFMj2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SYOKcYGkn5tGFMj2 .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-SYOKcYGkn5tGFMj2 g.clickable{cursor:pointer;}#mermaid-svg-SYOKcYGkn5tGFMj2 g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-SYOKcYGkn5tGFMj2 g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-SYOKcYGkn5tGFMj2 .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-SYOKcYGkn5tGFMj2 .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-SYOKcYGkn5tGFMj2 .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-SYOKcYGkn5tGFMj2 .dashed-line{stroke-dasharray:3;}#mermaid-svg-SYOKcYGkn5tGFMj2 #compositionStart,#mermaid-svg-SYOKcYGkn5tGFMj2 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-SYOKcYGkn5tGFMj2 #compositionEnd,#mermaid-svg-SYOKcYGkn5tGFMj2 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-SYOKcYGkn5tGFMj2 #dependencyStart,#mermaid-svg-SYOKcYGkn5tGFMj2 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-SYOKcYGkn5tGFMj2 #dependencyStart,#mermaid-svg-SYOKcYGkn5tGFMj2 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-SYOKcYGkn5tGFMj2 #extensionStart,#mermaid-svg-SYOKcYGkn5tGFMj2 .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-SYOKcYGkn5tGFMj2 #extensionEnd,#mermaid-svg-SYOKcYGkn5tGFMj2 .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-SYOKcYGkn5tGFMj2 #aggregationStart,#mermaid-svg-SYOKcYGkn5tGFMj2 .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-SYOKcYGkn5tGFMj2 #aggregationEnd,#mermaid-svg-SYOKcYGkn5tGFMj2 .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-SYOKcYGkn5tGFMj2 .edgeTerminals{font-size:11px;}#mermaid-svg-SYOKcYGkn5tGFMj2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} «interface» Command execute() undo() MacroCommand -Command[] commands execute() : CallExecuteOfEachCommand undo() : CallUndoOfEachCommand 因为 MacroCommand 实现 Command所以 MacroCommand 可以作为普通的具体命令使用因为 MacroCommand 组合 Command所以 MacroCommand 可以借助一系列的具体命令对象执行操作。
下面是 MacroCommand 类的定义
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() {// 按照后执行先撤销的顺序调用 undo()for (int i commands.length - 1; i 0; i--) {commands[i].undo();}}
}一键关灯的代码示意如下
RemoteControl remoteControl new RemoteControl(10);Light livingRoomLight new Light(Living Room);
Light kitchenLight new Light(Kitchen);LightOffCommand livingRoomLightOff new LightOffCommand(livingRoomLight);
LightOffCommand kitchenLightOff new LightOffCommand(kitchenLight);Command[] lightsOffCommands { livingRoomLightOff, kitchenLightOff };
remoteControl.setCommand(4, new MacroCommand(lightsOffCommands));remoteControl.buttonWasPushed(4);
remoteControl.undoButtonWasPushed();3.4 命令模式定义
刚刚我们已经采用“命令模式”完成了家居自动化遥控器的 API 设计。下面是命令模式的正式定义 命令模式Command Pattern 将一个请求封装为一个对象从而使你可用不同的请求对客户进行参数化对请求排队或记录请求日志以及支持可撤销的操作。 Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. 这里的客户Client指的是使用命令的用户即调用者 Invoker可以使用不同的命令对象来配置调用者的行为。 #mermaid-svg-uWm4T0jcPMVVRFVR {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-uWm4T0jcPMVVRFVR .error-icon{fill:#552222;}#mermaid-svg-uWm4T0jcPMVVRFVR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uWm4T0jcPMVVRFVR .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-uWm4T0jcPMVVRFVR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uWm4T0jcPMVVRFVR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uWm4T0jcPMVVRFVR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uWm4T0jcPMVVRFVR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uWm4T0jcPMVVRFVR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uWm4T0jcPMVVRFVR .marker.cross{stroke:#333333;}#mermaid-svg-uWm4T0jcPMVVRFVR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uWm4T0jcPMVVRFVR g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-uWm4T0jcPMVVRFVR g.classGroup text .title{font-weight:bolder;}#mermaid-svg-uWm4T0jcPMVVRFVR .nodeLabel,#mermaid-svg-uWm4T0jcPMVVRFVR .edgeLabel{color:#131300;}#mermaid-svg-uWm4T0jcPMVVRFVR .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-uWm4T0jcPMVVRFVR .label text{fill:#131300;}#mermaid-svg-uWm4T0jcPMVVRFVR .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-uWm4T0jcPMVVRFVR .classTitle{font-weight:bolder;}#mermaid-svg-uWm4T0jcPMVVRFVR .node rect,#mermaid-svg-uWm4T0jcPMVVRFVR .node circle,#mermaid-svg-uWm4T0jcPMVVRFVR .node ellipse,#mermaid-svg-uWm4T0jcPMVVRFVR .node polygon,#mermaid-svg-uWm4T0jcPMVVRFVR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uWm4T0jcPMVVRFVR .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-uWm4T0jcPMVVRFVR g.clickable{cursor:pointer;}#mermaid-svg-uWm4T0jcPMVVRFVR g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-uWm4T0jcPMVVRFVR g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-uWm4T0jcPMVVRFVR .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-uWm4T0jcPMVVRFVR .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-uWm4T0jcPMVVRFVR .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-uWm4T0jcPMVVRFVR .dashed-line{stroke-dasharray:3;}#mermaid-svg-uWm4T0jcPMVVRFVR #compositionStart,#mermaid-svg-uWm4T0jcPMVVRFVR .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-uWm4T0jcPMVVRFVR #compositionEnd,#mermaid-svg-uWm4T0jcPMVVRFVR .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-uWm4T0jcPMVVRFVR #dependencyStart,#mermaid-svg-uWm4T0jcPMVVRFVR .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-uWm4T0jcPMVVRFVR #dependencyStart,#mermaid-svg-uWm4T0jcPMVVRFVR .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-uWm4T0jcPMVVRFVR #extensionStart,#mermaid-svg-uWm4T0jcPMVVRFVR .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-uWm4T0jcPMVVRFVR #extensionEnd,#mermaid-svg-uWm4T0jcPMVVRFVR .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-uWm4T0jcPMVVRFVR #aggregationStart,#mermaid-svg-uWm4T0jcPMVVRFVR .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-uWm4T0jcPMVVRFVR #aggregationEnd,#mermaid-svg-uWm4T0jcPMVVRFVR .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-uWm4T0jcPMVVRFVR .edgeTerminals{font-size:11px;}#mermaid-svg-uWm4T0jcPMVVRFVR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} «interface» Command execute() undo() ConcreteCommand -Receiver receiver -state ConcreteCommand(Receiver) execute() : CallActionOfReceiver undo() : ReverseTheAction Receiver action() Client Invoker -Command command setCommand(Command) executeCommand() undoCommand() Command抽象命令接口 声明抽象的 execute() 方法用于提交请求声明抽象的 undo() 方法用于撤销已经提交的请求恢复到请求执行前的状态。 ConcreteCommand具体命令实现类 声明 receiver 实例变量作为命令的接收者 如果具体命令可以独立完成请求那么就不需要接收者如果命令的执行还需要其它参数那么可以声明更多的实例变量来提供参数设置。 实现 execute() 方法 首先通过 state 保存当前状态如果该请求是可撤销的并且需要保存状态然后调用 receiver.action() 执行请求 实现 undo() 方法恢复 state 状态取消执行 execute() 的效果。 Client 创建 ConcreteCommand 对象并指定它的 Receiver 对象为 Invoker 对象设置 ConcreteCommand 对象 因为 ConcreteCommand 对象可以被传递、被存储 所以几经辗转之后可能会由 OtherClient 来为 Invoker 对象设置 ConcreteCommand 对象。 通过 Command 将 Invoker 和 Receiver 解耦。 Invoker命令调用者 声明 command 实例变量引用 ConcreteCommand 对象在需要提交请求时调用 command.execute()在需要撤销请求时调用 command.undo()。如果该请求是可撤销的 Receiver命令接收者 定义 action() 方法执行请求对应的具体操作。
参与者之间的交互如下 #mermaid-svg-q8KjF3d812NrhoId {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-q8KjF3d812NrhoId .error-icon{fill:#552222;}#mermaid-svg-q8KjF3d812NrhoId .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-q8KjF3d812NrhoId .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-q8KjF3d812NrhoId .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-q8KjF3d812NrhoId .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-q8KjF3d812NrhoId .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-q8KjF3d812NrhoId .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-q8KjF3d812NrhoId .marker{fill:#333333;stroke:#333333;}#mermaid-svg-q8KjF3d812NrhoId .marker.cross{stroke:#333333;}#mermaid-svg-q8KjF3d812NrhoId svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-q8KjF3d812NrhoId .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-q8KjF3d812NrhoId text.actortspan{fill:black;stroke:none;}#mermaid-svg-q8KjF3d812NrhoId .actor-line{stroke:grey;}#mermaid-svg-q8KjF3d812NrhoId .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-q8KjF3d812NrhoId .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-q8KjF3d812NrhoId #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-q8KjF3d812NrhoId .sequenceNumber{fill:white;}#mermaid-svg-q8KjF3d812NrhoId #sequencenumber{fill:#333;}#mermaid-svg-q8KjF3d812NrhoId #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-q8KjF3d812NrhoId .messageText{fill:#333;stroke:#333;}#mermaid-svg-q8KjF3d812NrhoId .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-q8KjF3d812NrhoId .labelText,#mermaid-svg-q8KjF3d812NrhoId .labelTexttspan{fill:black;stroke:none;}#mermaid-svg-q8KjF3d812NrhoId .loopText,#mermaid-svg-q8KjF3d812NrhoId .loopTexttspan{fill:black;stroke:none;}#mermaid-svg-q8KjF3d812NrhoId .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-q8KjF3d812NrhoId .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-q8KjF3d812NrhoId .noteText,#mermaid-svg-q8KjF3d812NrhoId .noteTexttspan{fill:black;stroke:none;}#mermaid-svg-q8KjF3d812NrhoId .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-q8KjF3d812NrhoId .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-q8KjF3d812NrhoId .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-q8KjF3d812NrhoId .actorPopupMenu{position:absolute;}#mermaid-svg-q8KjF3d812NrhoId .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-q8KjF3d812NrhoId .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-q8KjF3d812NrhoId .actor-man circle,#mermaid-svg-q8KjF3d812NrhoId line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-q8KjF3d812NrhoId :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} aReceiver aClient aCommand anInvoker new Command(aReceiver) setCommand(aCommand) execute() action() aReceiver aClient aCommand anInvoker 模式效果
命令是一等对象可以像其他对象一样被操作如创建、传递、存储和扩展通过继承或组合【优点】降低耦合度将调用操作的对象Invoker与知道如何实现该操作的对象Receiver解耦【优点】易于扩展增加新的 ConcreteCommand 时不需要改变已有的类【缺点】增加复杂度可能会定义过多的具体命令类增加系统的复杂度。
延伸阅读《设计模式可复用面向对象软件的基础》 5.2 Command命令— 对象行为型模式 [P175-183] 4 示例代码 4.1 Java 示例
厂商设备类的定义
// Light.java
public class Light {String location;public Light(String location) { this.location location; }public void on() { System.out.println(location light is on); }public void off() { System.out.println(location light is off); }
}// CeilingFan.java
public class CeilingFan {public static final int HIGH 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 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;}
}命令接口和实现类的定义
// Command.java
public interface Command {public void execute();public void undo();
}// NoCommand.java 空对象模式实现命令但不做任何操作
public class NoCommand implements Command {public void execute() {}public void undo() { }
}// MacroCommand.java
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();}}// these commands have to be done backwards to ensure proper undo functionalitypublic void undo() {for (int i commands.length - 1; i 0; i--) {commands[i].undo();}}
}// LightOnCommand.java
public class LightOnCommand implements Command {Light light;public LightOnCommand(Light light) { this.light light; }public void execute() { light.on(); }public void undo() { light.off(); }
}// LightOffCommand.java
public class LightOffCommand implements Command {Light light;public LightOffCommand(Light light) { this.light light; }public void execute() { light.off(); }public void undo() { light.on(); }
}// CeilingFanCommand.java 通过抽象基类实现 undo() 方法在子类中实现 execute() 方法
public abstract class CeilingFanCommand implements Command {protected CeilingFan ceilingFan;protected int prevSpeed;public CeilingFanCommand(CeilingFan ceilingFan) { this.ceilingFan ceilingFan; }Overridepublic abstract void execute();Overridepublic void undo() {switch (prevSpeed) {case CeilingFan.HIGH: ceilingFan.high(); break;case CeilingFan.LOW: ceilingFan.low(); break;case CeilingFan.OFF: ceilingFan.off(); break;}}
}// CeilingFanHighCommand.java
public class CeilingFanHighCommand extends CeilingFanCommand {public CeilingFanHighCommand(CeilingFan ceilingFan) { super(ceilingFan); }public void execute() {prevSpeed ceilingFan.getSpeed();ceilingFan.high();}
}// CeilingFanLowCommand.java
public class CeilingFanLowCommand extends CeilingFanCommand {public CeilingFanLowCommand(CeilingFan ceilingFan) { super(ceilingFan); }public void execute() {prevSpeed ceilingFan.getSpeed();ceilingFan.low();}
}// CeilingFanOffCommand.java
public class CeilingFanOffCommand extends CeilingFanCommand {public CeilingFanOffCommand(CeilingFan ceilingFan) { super(ceilingFan); }public void execute() {prevSpeed ceilingFan.getSpeed();ceilingFan.off();}
}遥控器的定义
// RemoteControl.java
public class RemoteControl {private Command[] commands;private Command undoCommand;RemoteControl(int buttonCount) {commands new Command[buttonCount];// 将所有命令都初始化为一个空对象这样在执行命令时就不需要进行空指针检查Command noCommand new NoCommand();for (int i 0; i buttonCount; i) {commands[i] noCommand;}undoCommand noCommand;}public void setCommand(int buttonIndex, Command command) {commands[buttonIndex] command;}void buttonWasPushed(int buttonIndex) {commands[buttonIndex].execute();undoCommand commands[buttonIndex];}void undoButtonWasPushed() {undoCommand.undo();}public String toString() {StringBuffer stringBuf new StringBuffer();stringBuf.append(\n------ Remote Control ------\n);for (int i 0; i commands.length; i) {stringBuf.append([button i ] commands[i].getClass().getName() \n);}stringBuf.append([undo] undoCommand.getClass().getName() \n);return stringBuf.toString();}
}测试代码
// RemoteLoader.java
public class RemoteLoader {public static void main(String args[]) {// 遥控器实例InvokerRemoteControl remoteControl new RemoteControl(10);// 设备实例ReceiverLight livingRoomLight new Light(Living Room);Light kitchenLight new Light(Kitchen);CeilingFan ceilingFan new CeilingFan(Living Room);// 命令实例ConcreteCommandLightOnCommand livingRoomLightOn new LightOnCommand(livingRoomLight);LightOffCommand livingRoomLightOff new LightOffCommand(livingRoomLight);LightOnCommand kitchenLightOn new LightOnCommand(kitchenLight);LightOffCommand kitchenLightOff new LightOffCommand(kitchenLight);Command[] lightsOffCommands { livingRoomLightOff, kitchenLightOff };MacroCommand lightsOffMacro new MacroCommand(lightsOffCommands);CeilingFanHighCommand ceilingFanHigh new CeilingFanHighCommand(ceilingFan);CeilingFanLowCommand ceilingFanLow new CeilingFanLowCommand(ceilingFan);CeilingFanOffCommand ceilingFanOff new CeilingFanOffCommand(ceilingFan);// 设置遥控器按钮对应的命令Invoker::setCommandremoteControl.setCommand(0, livingRoomLightOn);remoteControl.setCommand(1, livingRoomLightOff);remoteControl.setCommand(2, kitchenLightOn);remoteControl.setCommand(3, kitchenLightOff);remoteControl.setCommand(4, lightsOffMacro);remoteControl.setCommand(5, ceilingFanHigh);remoteControl.setCommand(6, ceilingFanLow);remoteControl.setCommand(7, ceilingFanOff);System.out.println(remoteControl);// 执行命令Invoker::executeCommand和 撤销命令Invoker::undoCommandremoteControl.buttonWasPushed(0); // 客厅灯打开remoteControl.buttonWasPushed(1); // 客厅灯关闭remoteControl.undoButtonWasPushed(); // 撤销关闭客厅灯打开remoteControl.buttonWasPushed(2); // 厨房灯打开remoteControl.buttonWasPushed(3); // 厨房灯关闭remoteControl.undoButtonWasPushed(); // 撤销关闭厨房灯打开remoteControl.buttonWasPushed(4); // 关闭所有灯remoteControl.undoButtonWasPushed(); // 撤销关闭打开所有灯remoteControl.buttonWasPushed(5); // 风扇高速remoteControl.buttonWasPushed(6); // 风扇低速remoteControl.undoButtonWasPushed(); // 撤销低速风扇高速remoteControl.buttonWasPushed(7); // 风扇关闭}
}4.2 C11 示例
厂商设备类的定义
struct Light {Light(const std::string location) : location(location) {}void on() { std::cout location light is on\n; }void off() { std::cout location light is off\n; }private:std::string location;
};struct CeilingFan {enum class Speed { OFF, LOW, HIGH };CeilingFan(const std::string location) : location(location) {}void high() {speed Speed::HIGH;std::cout location ceiling fan is on high\n;}void low() {speed Speed::LOW;std::cout location ceiling fan is on low\n;}void off() {speed Speed::OFF;std::cout location ceiling fan is off\n;}Speed getSpeed() const { return speed; }private:std::string location;Speed speed Speed::OFF;
};命令接口和实现类的定义
struct Command {virtual ~Command() default;virtual void execute() 0;virtual void undo() 0;
};// 空对象模式实现命令但不做任何操作
struct NoCommand : Command {void execute() override {}void undo() override {}
};struct MacroCommand : Command {MacroCommand(const std::vectorstd::shared_ptrCommand commands) : commands(commands) {}void execute() override {for (const auto command : commands) {command-execute();}}// these commands have to be done backwards to ensure proper undo functionalityvoid undo() override {for (auto it commands.rbegin(); it ! commands.rend(); it) {(*it)-undo();}}private:std::vectorstd::shared_ptrCommand commands;
};struct LightOnCommand : Command {LightOnCommand(Light light) : light(light) {}void execute() override { light.on(); }void undo() override { light.off(); }private:Light light;
};struct LightOffCommand : Command {LightOffCommand(Light light) : light(light) {}void execute() override { light.off(); }void undo() override { light.on(); }private:Light light;
};// 通过抽象基类实现 undo() 方法在子类中实现 execute() 方法
struct CeilingFanCommand : Command {CeilingFanCommand(CeilingFan ceilingFan) : ceilingFan(ceilingFan) {}virtual void execute() override 0;void undo() override {switch (prevSpeed) {case CeilingFan::Speed::HIGH: ceilingFan.high(); break;case CeilingFan::Speed::LOW: ceilingFan.low(); break;case CeilingFan::Speed::OFF: ceilingFan.off(); break;}}protected:CeilingFan ceilingFan;CeilingFan::Speed prevSpeed;
};struct CeilingFanHighCommand : CeilingFanCommand {CeilingFanHighCommand(CeilingFan ceilingFan) : CeilingFanCommand(ceilingFan) {}void execute() override {prevSpeed ceilingFan.getSpeed();ceilingFan.high();}
};struct CeilingFanLowCommand : CeilingFanCommand {CeilingFanLowCommand(CeilingFan ceilingFan) : CeilingFanCommand(ceilingFan) {}void execute() override {prevSpeed ceilingFan.getSpeed();ceilingFan.low();}
};struct CeilingFanOffCommand : CeilingFanCommand {CeilingFanOffCommand(CeilingFan ceilingFan) : CeilingFanCommand(ceilingFan) {}void execute() override {prevSpeed ceilingFan.getSpeed();ceilingFan.off();}
};遥控器的定义
struct RemoteControl {explicit RemoteControl(int buttonCount) {// 将所有命令都初始化为一个空对象这样在执行命令时就不需要进行空指针检查auto noCommand std::make_sharedNoCommand();commands std::vectorstd::shared_ptrCommand(buttonCount, noCommand);undoCommand noCommand;}void setCommand(int buttonIndex, std::shared_ptrCommand command) {commands[buttonIndex] command; }void buttonWasPushed(int buttonIndex) {commands[buttonIndex]-execute();undoCommand commands[buttonIndex];}void undoButtonWasPushed() const {undoCommand-undo(); }private:std::vectorstd::shared_ptrCommand commands;std::shared_ptrCommand undoCommand;friend std::ostream operator(std::ostream os, const RemoteControl remoteControl);
};std::ostream operator(std::ostream os, const RemoteControl remoteControl) {os \n------ Remote Control ------\n;for (int i 0; i remoteControl.commands.size(); i) {os [button i ] typeid(*remoteControl.commands[i]).name() \n;}os [undo] typeid(*remoteControl.undoCommand).name() \n\n;return os;
}测试代码
#include iostream
#include memory
#include string
#include typeinfo
#include vector// 在这里添加相关接口和类的定义int main() {// 遥控器实例InvokerRemoteControl remoteControl(10);// 设备实例ReceiverLight livingRoomLight(Living Room);Light kitchenLight(Kitchen);CeilingFan ceilingFan(Living Room);// 命令实例ConcreteCommandauto livingRoomLightOn std::make_sharedLightOnCommand(livingRoomLight);auto livingRoomLightOff std::make_sharedLightOffCommand(livingRoomLight);auto kitchenLightOn std::make_sharedLightOnCommand(kitchenLight);auto kitchenLightOff std::make_sharedLightOffCommand(kitchenLight);std::vectorstd::shared_ptrCommand lightsOffCommands {livingRoomLightOff, kitchenLightOff};auto lightsOffMacro std::make_sharedMacroCommand(lightsOffCommands);auto ceilingFanHigh std::make_sharedCeilingFanHighCommand(ceilingFan);auto ceilingFanLow std::make_sharedCeilingFanLowCommand(ceilingFan);auto ceilingFanOff std::make_sharedCeilingFanOffCommand(ceilingFan);// 设置遥控器按钮对应的命令Invoker::setCommandremoteControl.setCommand(0, livingRoomLightOn);remoteControl.setCommand(1, livingRoomLightOff);remoteControl.setCommand(2, kitchenLightOn);remoteControl.setCommand(3, kitchenLightOff);remoteControl.setCommand(4, lightsOffMacro);remoteControl.setCommand(5, ceilingFanHigh);remoteControl.setCommand(6, ceilingFanLow);remoteControl.setCommand(7, ceilingFanOff);std::cout remoteControl;// 执行命令Invoker::executeCommand和 撤销命令Invoker::undoCommandremoteControl.buttonWasPushed(0); // 客厅灯打开remoteControl.buttonWasPushed(1); // 客厅灯关闭remoteControl.undoButtonWasPushed(); // 撤销关闭客厅灯打开remoteControl.buttonWasPushed(2); // 厨房灯打开remoteControl.buttonWasPushed(3); // 厨房灯关闭remoteControl.undoButtonWasPushed(); // 撤销关闭厨房灯打开remoteControl.buttonWasPushed(4); // 关闭所有灯remoteControl.undoButtonWasPushed(); // 撤销关闭打开所有灯remoteControl.buttonWasPushed(5); // 风扇高速remoteControl.buttonWasPushed(6); // 风扇低速remoteControl.undoButtonWasPushed(); // 撤销低速风扇高速remoteControl.buttonWasPushed(7); // 风扇关闭
}5 设计工具箱 5.1 OO 基础
OO 基础回顾
抽象Abstraction封装Encapsulation继承Inheritance多态Polymorphism 5.2 OO 原则
5.2.1 新原则
最近两章都没有介绍新的 OO 原则。
5.2.2 原则回顾
封装变化。 Encapsulate what varies.针对接口编程而不是针对实现编程。 Program to interfaces, not implementations.优先使用组合而不是继承。 Favor composition over inheritance.尽量做到交互对象之间的松耦合设计。 Strive for loosely coupled designs between objects that interact.类应该对扩展开放对修改关闭。 Classes should be open for extension, but closed for modification.依赖抽象不依赖具体类。 Depend on abstractions. Do not depend on concrete classes. 5.3 OO 模式
5.3.1 新模式
命令模式Command Pattern
把请求封装为对象 The Command Pattern encapsulates a request as an object,以便用不同的请求来参数化客户对请求进行排队或记录请求日志并支持可撤销的操作。 thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
5.3.2 模式回顾 1 创建型模式Creational Patterns 创建型模式与对象的创建有关。 Creational patterns concern the process of object creation. 工厂方法Factory Method 定义了一个创建对象的接口但由子类决定要实例化哪个类。 The Factory Method Pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate.工厂方法让类把实例化推迟到子类。 Factory Method lets a class defer instantiation to subclasses. 抽象工厂Abstract Factory 提供一个接口创建相关或依赖对象的家族而不需要指定具体类。 The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. 单例模式Singleton Pattern 确保一个类只有一个实例并提供一个全局访问点。 The Singleton Pattern ensures a class has only one instance, and provides a global point of access to it. 2 结构型模式Structural Patterns 结构型模式处理类或对象的组合。 Structural patterns deal with the composition of classes or objects. 装饰者模式Decorator Pattern 动态地给一个对象添加一些额外的职责。 The Decorator Pattern attaches additional responsibilities to an object dynamically.就增加功能来说装饰者模式相比生成子类更为灵活。 Decorators provide a flexible alternative to subclassing for extending functionality. 3 行为型模式Behavioral Patterns 行为型模式描述类或对象之间的交互方式以及职责分配方式。 Behavioral patterns characterize the ways in which classes or objects interact and distribute responsibility. 策略模式Strategy Pattern 定义一个算法家族把其中的算法分别封装起来使得它们之间可以互相替换。 Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable.让算法的变化独立于使用算法的客户。 Strategy lets the algorithm vary independently from clients that use it. 观察者模式Observer Pattern 定义对象之间的一对多依赖 The Observer Pattern defines a one-to-many dependency between objects这样一来当一个对象改变状态时它的所有依赖者都会被通知并自动更新。 so that when one object changes state, all of its dependents are notified and updated automatically.
参考
[美]弗里曼、罗布森著,UMLChina译.Head First设计模式.中国电力出版社.2022.2[美]伽玛等著,李英军等译.设计模式可复用面向对象软件的基础.机械工业出版社.2019.3wickedlysmart: Head First设计模式 Java 源码 Hi, I’m the ENDing, nice to meet you here! Hope this article has been helpful.