【HF设计模式】06-命令模式
声明:仅为个人学习总结,还请批判性查看,如有不同观点,欢迎交流。
摘要
《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 C++11 示例
- 5 设计工具箱
- 5.1 OO 基础
- 5.2 OO 原则
- 5.3 OO 模式
- 参考
1 示例应用
本章的示例是,为家居自动化遥控器设计编程接口(API)。
遥控器有着众多款式。
它们的共同点是:
- 表面分布着各种按钮,用于控制各种家居自动化设备;
- 包含一个全局撤销按钮,用于取消最近一次按钮操作的效果。
家居自动化设备包括电灯、风扇、音响、洒水器等等,它们由多家厂商提供,每种设备都有对应的 Java 类,用于进行自动化控制。
现有设备类的接口如下:(接口各异,并没有统一的定义)
现在需要为遥控器创建一套 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 对象,作为命令的执行者/接收者(Receiver)public 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 系统类图
LightOnCommand
和LightOffCommand
- 实现
Command
接口; - 通过封装
Light
对象及其操作,来执行请求。
注:由于当前 mermaid 类图不支持 note,所以方法(method)的返回类型都被用于作为注释,如 CallOnOfLight
- 实现
RemoteControl
(命令调用者)- 通过
Command
对象提交请求; - 与
Light
(命令接收者)解耦。
- 通过
RemoteLoader
- 创建命令对象,并将其设置给命令调用者。
3.2 实现撤销
撤销的核心思想是:每个命令都需要能够“撤销”自己的执行效果。
撤销的实现方式为:
- 在
Command
接口中声明抽象方法undo()
; - 由具体命令实现
undo()
方法;- 对于无状态撤销: 直接执行反向操作(如开灯->关灯)
- 对于有状态撤销: 恢复之前保存的状态(如风扇速度)
- 保存最近一次执行的命令,在需要撤销时,调用命令的
undo()
方法。
3.2.1 声明 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()
方法,还可以将命令写到日志中,并在需要的时候重新加载和执行命令。
(这项功能对于当前的遥控器来说,并没有意义;但是在一些数据处理应用中,通过记录日志,可以在异常发生后实现数据恢复。)
3.3 组合命令
为了实现“使用一个按钮,关闭所有室内灯”,我们需要引入一个新的命令类型 MacroCommand
:
- 因为
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
),可以使用不同的命令对象来配置调用者的行为。
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()
方法,执行请求对应的具体操作。
- 定义
参与者之间的交互如下:
模式效果:
- 命令是一等对象,可以像其他对象一样被操作(如创建、传递、存储)和扩展(通过继承或组合);
- 【优点】降低耦合度:将调用操作的对象(
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[]) {// 遥控器实例(Invoker)RemoteControl remoteControl = new RemoteControl(10);// 设备实例(Receiver)Light livingRoomLight = new Light("Living Room");Light kitchenLight = new Light("Kitchen");CeilingFan ceilingFan = new CeilingFan("Living Room");// 命令实例(ConcreteCommand)LightOnCommand 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::setCommand)remoteControl.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::undoCommand)remoteControl.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 C++11 示例
厂商设备类的定义:
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::vector<std::shared_ptr<Command>> &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::vector<std::shared_ptr<Command>> 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_shared<NoCommand>();commands = std::vector<std::shared_ptr<Command>>(buttonCount, noCommand);undoCommand = noCommand;}void setCommand(int buttonIndex, std::shared_ptr<Command> command) {commands[buttonIndex] = command; }void buttonWasPushed(int buttonIndex) {commands[buttonIndex]->execute();undoCommand = commands[buttonIndex];}void undoButtonWasPushed() const {undoCommand->undo(); }private:std::vector<std::shared_ptr<Command>> commands;std::shared_ptr<Command> 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() {// 遥控器实例(Invoker)RemoteControl remoteControl(10);// 设备实例(Receiver)Light livingRoomLight("Living Room");Light kitchenLight("Kitchen");CeilingFan ceilingFan("Living Room");// 命令实例(ConcreteCommand)auto livingRoomLightOn = std::make_shared<LightOnCommand>(livingRoomLight);auto livingRoomLightOff = std::make_shared<LightOffCommand>(livingRoomLight);auto kitchenLightOn = std::make_shared<LightOnCommand>(kitchenLight);auto kitchenLightOff = std::make_shared<LightOffCommand>(kitchenLight);std::vector<std::shared_ptr<Command>> lightsOffCommands = {livingRoomLightOff, kitchenLightOff};auto lightsOffMacro = std::make_shared<MacroCommand>(lightsOffCommands);auto ceilingFanHigh = std::make_shared<CeilingFanHighCommand>(ceilingFan);auto ceilingFanLow = std::make_shared<CeilingFanLowCommand>(ceilingFan);auto ceilingFanOff = std::make_shared<CeilingFanOffCommand>(ceilingFan);// 设置遥控器按钮对应的命令(Invoker::setCommand)remoteControl.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::undoCommand)remoteControl.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.3
- wickedlysmart: Head First设计模式 Java 源码
Hi, I’m the ENDing, nice to meet you here! Hope this article has been helpful.
相关文章:

【HF设计模式】06-命令模式
声明:仅为个人学习总结,还请批判性查看,如有不同观点,欢迎交流。 摘要 《Head First设计模式》第6章笔记:结合示例应用和代码,介绍命令模式,包括遇到的问题、采用的解决方案、遵循的 OO 原则、…...
Linux使用SSH连接GitHub指南
基础配置流程 步骤1:生成SSH密钥 打开终端:首先,打开你的Linux终端。 生成SSH密钥对:输入以下命令来生成一个新的SSH密钥对: ssh-keygen -t rsa -b 4096 -C "your_email@example.com"-t rsa:使用RSA加密算法生成密钥。-b 4096:密钥长度为4096位,增加安全性。…...
v2富文本框封装 @wangeditor/editor-for-vue
1 组件封装 <template><div class"editor-container"><div class"editor-wrapper"><Toolbarstyle"border-bottom: 1px solid #ccc":editor"editor":defaultConfig"toolbarConfig":mode"mode&quo…...
【分类】【损失函数】处理类别不平衡:CEFL 和 CEFL2 损失函数的实现与应用
引言 在深度学习中的分类问题中,类别不平衡问题是常见的挑战之一。尤其在面部表情分类任务中,不同表情类别的样本数量可能差异较大,比如“开心”表情的样本远远多于“生气”表情。面对这种情况,普通的交叉熵损失函数容易导致模型…...
AUTOSAR从入门到精通-自动驾驶测试技术
目录 前言 算法原理 测试场景定义与作用 测试场景要素 测试场景分类 场景信息提取与挖掘方法 自动驾驶感知测试分类 自动驾驶图像系统测试 自动驾驶激光雷达系统测试 自动驾驶融合感知系统测试 自动驾驶仿真测试 1. 功能安全 2. 预期功能安全 3. 软件测试 4.敏捷…...
优化大型语言模型的表达能力和依赖关系:理论
摘要 随着自然语言处理技术的发展,大型语言模型(LLM)已经成为理解和生成人类语言的强大工具。然而,如何有效提升这些模型的表达能力以及捕捉长距离依赖关系仍然是一个挑战。本文通过具体实例探讨了词表大小(em_size&a…...

在Ubuntu下使用Wine运行MobaXterm并解决X服务器问题
MobaXterm是一款功能强大的终端模拟器,集成了SSH客户端和X服务器,常用于远程服务器管理。在Ubuntu下,我们可以通过Wine运行MobaXterm,同时解决X服务器问题,实现远程图形界面转发。这对于需要远程使用ROS(如…...

【鸿蒙】0x02-LiteOS-M基于Qemu RISC-V运行
OpenHarmony LiteOS-M基于Qemu RISC-V运行 系列文章目录更新日志OpenHarmony技术架构OH技术架构OH支持系统类型轻量系统(mini system)小型系统(small system)标准系统(standard system) 简介环境准备安装QE…...

SW - 钣金零件保存成DWG时,需要将折弯线去掉
文章目录 SW - 钣金零件保存成DWG时,需要将折弯线去掉概述笔记备注END SW - 钣金零件保存成DWG时,需要将折弯线去掉 概述 如果做需要弯折的切割件,最好做成钣金零件。 最近做了几个小钣金(将钣金展开,建立新草图,在2…...

JAVA使用自定义注解,在项目中实现EXCEL文件的导出
首先定义一个注解 Retention(RetentionPolicy.RUNTIME) Target(ElementType.FIELD) public interface Excel {/*** 导出时在excel中排序*/int sort() default Integer.MAX_VALUE;/*** 导出到Excel中的名字.*/String name() default "";/*** 首行字段的批注*/String …...

【GIS操作】使用ArcGIS Pro进行海图的地理配准(附:墨卡托投影对比解析)
文章目录 一、应用场景二、墨卡托投影1、知识点2、Arcgis中的坐标系选择 三、操作步骤1、数据转换2、数据加载3、栅格投影4、地理配准 一、应用场景 地理配准是数字化之前必须进行的一项工作。扫描得到的地图数据通常不包含空间参考信息,需要通过具有较高位置精度的…...
flutter在使用gradle时的加速
当我使用了一些过时的插件的时候,遇到了一些问题 比如什么namespace 问题等,因为有些插件库没有更新了,或者最新版本处于测试阶段 于是我就删除这些旧插件(不符合我要求的插件) 于是根据各论坛的解决方法去做了以下的工作 1:项目中删除了这…...

ABP - 缓存模块(1)
ABP - 缓存模块(1) 1. 与 .NET Core 缓存的关系和差异2. Abp 缓存的使用2.1 常规使用2.2 非字符串类型的 Key2.3 批量操作 3. 额外功能 1. 与 .NET Core 缓存的关系和差异 ABP 框架中的缓存系统核心包是 Volo.Abp.Caching ,而对于分布式缓存…...

二、点灯基础实验
嵌入式基础实验第一个就是点灯,地位相当于编程界的hello world。 如下为LED原理图,要让相应LED发光,需要给I/O口设置输出引脚,低电平,二极管才会导通 2.1 打开初始工程,编写代码 以下会实现BLINKY常亮&…...

双端队列实战 实现滑动窗口 用LinkedList的基类双端队列Deque实现 洛谷[P1886]
集合 关系 介绍 Deque 是一个接口 LinkedList 是这个接口的实现类 题目 输入输出 滑动窗口 基于双端队列实现 Deque<Integer> deque new LinkedList<>(); 滑动窗口代码 public static List<Integer> maxSlidingWindow(int[] nums, int k) {List<Int…...

HTML<img>标签
例子 如何插入图片: <img src"img_girl.jpg" alt"Girl in a jacket" width"500" height"600"> 下面有更多“自己尝试”的示例。 定义和用法 该<img>标签用于在 HTML 页面中嵌入图像。 从技术上讲&#x…...

【网络 MAC 学习专栏 -- 如何理解 PHY 的 Link Up】
请阅读【嵌入式开发学习必备专栏 Cache | MMU | AMBA BUS | CoreSight | Trace32 | CoreLink | ARM GCC | CSH】 文章目录 OverviewClause 22/Clause 45Clause 22Clause 45 PHY Link 状态的软件实现 转自: 开心果 Need Car 2022年10月20日 09:50 上海 Overview PHY…...

Linux虚拟机安装与FinalShell使用:探索Linux世界的便捷之旅
文章目录 软件准备安装 VMware 虚拟机下载CentOS 光盘镜像文件选择适合的 CentOS 版本选择合适的镜像文件 本教程工具版本 第一部分:安装 Linux 虚拟机1. 启动 VMware 并创建新虚拟机2. 默认硬件兼容性设置3. 安装操作系统的设置4. 选择操作系统类型与版本5. 为虚拟…...
Mixly米思齐1.0 2.0 3.0 软件windows版本MAC苹果电脑系统安装使用常见问题与解决
Mixly软件应用常见问题 Mixly米思齐编译或上传报错? 1、软件安装与驱动(Mixly1-2) 1-1 Windows版本 软件及驱动可以在Mixly群(QQ群号621937623)的群文件夹中找到,或到Mixly在线软件下载链接中重新下安装…...
vben5 admin ant design vue如何使用时间范围组件RangePicker
本文参考:https://pusdn-dev.feishu.cn/wiki/VF4hwBAUliTE6TkUPKrcBNcZn9f?fromfrom_copylink 由PUSDN整理发行,收录时请保留PUSDN。 前端组件专题 年月日时间范围表单回显RangePicker 推荐使用多个字段存储,不推荐用英文逗号拼接时间&am…...

【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...