【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)。

遥控器有着众多款式。

各种遥控器

它们的共同点是:

  1. 表面分布着各种按钮,用于控制各种家居自动化设备;
  2. 包含一个全局撤销按钮,用于取消最近一次按钮操作的效果。

家居自动化设备包括电灯、风扇、音响、洒水器等等,它们由多家厂商提供,每种设备都有对应的 Java 类,用于进行自动化控制。
现有设备类的接口如下:(接口各异,并没有统一的定义)

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. 将每个按钮与一个或一组家居设备绑定,通过按钮控制相应设备实现特定的功能。如下表所示:

    按钮编号绑定设备控制功能功能类型
    1号客厅灯开灯① 单一设备、单一操作
    2号客厅灯关灯① 单一设备、单一操作
    5号所有室内灯关灯② 多个设备、组合操作
    6号风扇高风速① 单一设备、单一操作
    9号电视打开,并设置1频道③ 单一设备、多项操作
  2. 通过撤销按钮,可以取消最近一次按钮操作(不包括撤销操作)的效果,恢复到操作前的状态;

  3. 能够支持现有的全部设备,以及厂商未来可能提供的任何设备。

遥控器 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() 方法中,变化的方面包括:

  • 设备对象的类型,例如 LightCeilingFanTV 等等;
  • 设备对象的操作,例如 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),

  1. 只需要按照 Command 接口的定义,统一调用 execute() 方法执行命令;
  2. 不再需要了解具体的设备类型、设备操作;
  3. 无论是增加新的设备类型,还是支持新的设备操作,都不需要修改 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 系统类图

«interface»
Command
+execute()
LightOnCommand
-Light light
+execute() : CallOnOfLight
LightOffCommand
-Light light
+execute() : CallOffOfLight
Light
+on()
+off()
RemoteLoader
RemoteControl
-Command[] commands
+setCommand()
-buttonWasPushed() : CallExecuteOfCommand
  1. LightOnCommandLightOffCommand
    • 实现 Command 接口;
    • 通过封装 Light 对象及其操作,来执行请求。
      注:由于当前 mermaid 类图不支持 note,所以方法(method)的返回类型都被用于作为注释,如 CallOnOfLight
  2. RemoteControl(命令调用者)
    • 通过 Command 对象提交请求;
    • Light(命令接收者)解耦。
  3. RemoteLoader
    • 创建命令对象,并将其设置给命令调用者。

3.2 实现撤销

撤销的核心思想是:每个命令都需要能够“撤销”自己的执行效果。
撤销的实现方式为:

  1. Command 接口中声明抽象方法 undo()
  2. 由具体命令实现 undo() 方法;
    • 对于无状态撤销: 直接执行反向操作(如开灯->关灯)
    • 对于有状态撤销: 恢复之前保存的状态(如风扇速度)
  3. 保存最近一次执行的命令,在需要撤销时,调用命令的 undo() 方法。

3.2.1 声明 undo() 方法

«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() 方法,还可以将命令写到日志中,并在需要的时候重新加载和执行命令。
(这项功能对于当前的遥控器来说,并没有意义;但是在一些数据处理应用中,通过记录日志,可以在异常发生后实现数据恢复。)

«interface»
Command
+execute()
+undo()
+store()
+load()

3.3 组合命令

为了实现“使用一个按钮,关闭所有室内灯”,我们需要引入一个新的命令类型 MacroCommand

«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),可以使用不同的命令对象来配置调用者的行为。

«interface»
Command
+execute()
+undo()
ConcreteCommand
-Receiver receiver
-state
+ConcreteCommand(Receiver)
+execute() : CallActionOfReceiver
+undo() : ReverseTheAction
Receiver
+action()
Client
Invoker
-Command command
+setCommand(Command)
+executeCommand()
+undoCommand()
  1. Command,抽象命令接口
    • 声明抽象的 execute() 方法,用于提交请求;
    • 声明抽象的 undo() 方法,用于撤销已经提交的请求(恢复到请求执行前的状态)。
  2. ConcreteCommand,具体命令实现类
    • 声明 receiver 实例变量,作为命令的接收者;
      • 如果具体命令可以独立完成请求,那么就不需要接收者;
      • 如果命令的执行还需要其它参数,那么可以声明更多的实例变量来提供参数设置。
    • 实现 execute() 方法,
      • 首先,通过 state 保存当前状态;(如果该请求是可撤销的,并且需要保存状态)
      • 然后,调用 receiver.action() 执行请求;
    • 实现 undo() 方法,恢复 state 状态,取消执行 execute() 的效果。
  3. Client
    • 创建 ConcreteCommand 对象,并指定它的 Receiver 对象;
    • Invoker 对象设置 ConcreteCommand 对象;
      • 因为 ConcreteCommand 对象可以被传递、被存储,
        所以,几经辗转之后,可能会由 OtherClient 来为 Invoker 对象设置 ConcreteCommand 对象。
    • 通过 CommandInvokerReceiver 解耦。
  4. Invoker,命令调用者
    • 声明 command 实例变量,引用 ConcreteCommand 对象;
    • 在需要提交请求时,调用 command.execute()
    • 在需要撤销请求时,调用 command.undo()。(如果该请求是可撤销的)
  5. Receiver,命令接收者
    • 定义 action() 方法,执行请求对应的具体操作。

参与者之间的交互如下:

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[]) {// 遥控器实例(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 基础回顾

  1. 抽象(Abstraction)
  2. 封装(Encapsulation)
  3. 继承(Inheritance)
  4. 多态(Polymorphism)

5.2 OO 原则

5.2.1 新原则

最近两章都没有介绍新的 OO 原则。

5.2.2 原则回顾

  1. 封装变化。
    Encapsulate what varies.
  2. 针对接口编程,而不是针对实现编程。
    Program to interfaces, not implementations.
  3. 优先使用组合,而不是继承。
    Favor composition over inheritance.
  4. 尽量做到交互对象之间的松耦合设计。
    Strive for loosely coupled designs between objects that interact.
  5. 类应该对扩展开放,对修改关闭。
    Classes should be open for extension, but closed for modification.
  6. 依赖抽象,不依赖具体类。
    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.

  1. 工厂方法(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.
  2. 抽象工厂(Abstract Factory)
    • 提供一个接口,创建相关或依赖对象的家族,而不需要指定具体类。
      The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.
  3. 单例模式(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.

  1. 装饰者模式(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.

  1. 策略模式(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.
  2. 观察者模式(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.

参考

  1. [美]弗里曼、罗布森著,UMLChina译.Head First设计模式.中国电力出版社.2022.2
  2. [美]伽玛等著,李英军等译.设计模式:可复用面向对象软件的基础.机械工业出版社.2019.3
  3. wickedlysmart: Head First设计模式 Java 源码

Hi, I’m the ENDing, nice to meet you here! Hope this article has been helpful.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/67803.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

记一次数据库连接 bug

整个的报错如下&#xff1a; com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server. Attempted reconnect 3 times. Giving up. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Metho…

【游戏设计原理】76 - 惩罚

惩罚是玩家在游戏中得到反馈的一种形式&#xff0c;可以认为是一种负反馈。 除了文中提到的几种惩罚机制&#xff08;“生命/游戏结束/继续”、“枯萎”、“永久死亡”&#xff09;&#xff0c;还有其他一些常见的惩罚类型&#xff0c;它们的设计主要目的是增加游戏的挑战性&a…

Java 基于 SpringBoot+Vue 的二手车交易系统(附源码,部署+文档)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

【Python使用】嘿马python高级进阶全体系教程第11篇:静态Web服务器-面向对象开发,1. 以面向对象的方式开发静态W

本教程的知识点为&#xff1a;操作系统 1. 常见的操作系统 4. 小结 ls命令选项 2. 小结 mkdir和rm命令选项 1. mkdir命令选项 压缩和解压缩命令 1. 压缩格式的介绍 2. tar命令及选项的使用 3. zip和unzip命令及选项的使用 4. 小结 编辑器 vim 1. vim 的介绍 2. vim 的工作模式 …

即现软著工具 - 让软著申请更高效

在软件著作权申请的过程中&#xff0c;开发者常常会遇到代码整理、统计和生成证明文件等繁琐且复杂的任务。为了解决这些问题&#xff0c;提高申请效率和成功率&#xff0c;给大家介绍一款工具&#xff1a;即现软著工具。 即现软著工具&#xff0c;能够快速整理软著申请的程序鉴…

一部手机如何配置内网电脑同时访问内外网

做过运维的朋友都知道&#xff0c;最麻烦的是运维电脑不能远程&#xff0c;每次都得现场进行维护&#xff0c;明明客户那边有可以访问内网的电脑&#xff0c;怎么操作能将这台电脑能访问跟到外网呢&#xff0c;这样不就能通过远程软件远程了吗&#xff1f;嘿嘿。按以下步骤试试…

Python网络自动化运维---SSH模块

目录 SSH建立过程 实验环境准备 一.SSH模块 1.1.Paramiko模块 1.1.1实验代码 1.1.2代码分段讲解 1.1.3代码运行过程 1.2Netmiko模块 Netmiko模块对比paramiko模块的改进&#xff1a; 1.2.1实验代码 1.2.2代码分段讲解 1.2.3代码运行过程 二.Paramiko模块和Ne…

Esxi下虚拟机磁盘类型厚置备改精简置备

Esxi虚拟机磁盘类型厚置备改精简置备 一、esxi报错磁盘不足 1.1、虚拟机报错磁盘不足 1.2、虚拟机磁盘类型 VMware vSphere 中有两种主要类型的虚拟硬盘&#xff1a;精简配置磁盘和厚置备磁盘。 厚置备磁盘有两种分配模型&#xff1a;厚置备延迟置零和厚置备置零。 三者比…

【MySQL系列文章】Linux环境下安装部署MySQL

前言 本次安装部署主要针对Linux环境进行安装部署操作,系统位数64 getconf LONG_BIT 64MySQL版本&#xff1a;v5.7.38 一、下载MySQL MySQL下载地址&#xff1a;MySQL :: Download MySQL Community Server (Archived Versions) 二、上传MySQL压缩包到Linuxx环境&#xff0c…

HTML 表单和输入标签详解

HTML 表单是网页与用户交互的重要工具&#xff0c;它允许用户输入数据并将其提交到服务器。表单在网页中的应用非常广泛&#xff0c;例如登录、注册、搜索、评论等功能都离不开表单。本文将详细介绍 HTML 表单及其相关标签的使用方法&#xff0c;帮助你全面掌握表单的设计与实现…

2024年,我的技术探索与成长之路

2024年&#xff0c;我的技术探索与成长之路 2024年已经过去&#xff0c;作为一名技术爱好者和写作者&#xff0c;我回顾了过去一年在博客上记录的点滴&#xff0c;感慨良多。这一年&#xff0c;我不仅见证了技术的飞速发展&#xff0c;也在不断学习和实践中找到了自己的成长方向…

《Long Context Compression with Activation Beacon》笔记

Activation Beacon出自智源与人大在2024年1月放在arxiv上的论文《Long Context Compression with Activation Beacon》(v1版的题目&#xff1a;Soaring from 4K to 400K: Extending LLM’s Context with Activation Beacon&#xff09;。它引入了Beacon token将上下文信息蒸馏到…

Jupyter notebook中运行dos指令运行方法

Jupyter notebook中运行dos指令运行方法 目录 Jupyter notebook中运行dos指令运行方法一、DOS(磁盘操作系统&#xff09;指令介绍1.1 DOS介绍1.2 DOS指令1.2.1 DIR - 显示当前目录下的文件和子目录列表。1.2.2 CD 或 CHDIR - 改变当前目录1.2.3 使用 CD .. 可以返回上一级目录1…

基于注解实现去重表消息防止重复消费

基于注解实现去重表消息防止重复消费 1. 背景/问题 在分布式系统中&#xff0c;消息队列&#xff08;如RocketMQ、Kafka&#xff09;的 消息重复消费 是常见问题&#xff0c;主要原因包括&#xff1a; 网络抖动&#xff1a;生产者或消费者因网络不稳定触发消息重发。消费者超…

Biotin sulfo-N-hydroxysuccinimide ester ;生物素磺基-N-羟基琥珀酰亚胺酯;生物素衍生物;190598-55-1

一、生物素及其衍生物的概述 生物素衍生物是指在生物素&#xff08;Vitamin H或B7&#xff09;分子基础上进行化学修饰得到的衍生化合物。这些衍生化合物在生物医学研究、临床诊断和药物开发等领域有着广泛的应用。 生物素&#xff08;Biotin&#xff09;是一种水溶性维生素&a…

Ubuntu如何安装redis服务?

环境&#xff1a; Ubuntu22.04 WSL2 问题描述&#xff1a; 如何安装redis服务&#xff1f; 解决方案&#xff1a; 1.在 Linux 上&#xff08;如 Ubuntu/Debian&#xff09;安装 1.通过包管理工具安装 Redis 服务器&#xff1a; sudo apt update sudo apt install redis…

Datawhale组队学习笔记task2——leetcode面试题

文章目录 写在前面Day5题目1.0112.路径总和解答2.0113路径总和II解答3.0101.对称二叉树解答 Day6题目1.0124.二叉树中的最大路径和解答2.0199.二叉树的右视图解答3.0226.翻转二叉树解答 Day7题目1.0105.从前序与中序遍历序列构造二叉树解答2.0098.验证二叉搜索树解答3.0110.平衡…

Flask简介与安装以及实现一个糕点店的简单流程

目录 1. Flask简介 1.1 Flask的核心特点 1.2 Flask的基本结构 1.3 Flask的常见用法 1.3.1 创建Flask应用 1.3.2 路由和视图函数 1.3.3 动态URL参数 1.3.4 使用模板 1.4 Flask的优点 1.5 总结 2. Flask 环境创建 2.1 创建虚拟环境 2.2 激活虚拟环境 1.3 安装Flask…

RFID系统安全认证协议及防碰撞算法研究(RFID Security)

目录 1.摘要 2.引言 3.前人研究成果 3.1 RFID系统协议模型 3.2 RFID系统安全认证协议分类 3.3 RFID安全认证协议及其研究 3.3.1 超轻量级安全认证协议及其研究 3.3.2 轻量级安全认证协议及其研究 3.3.2 中量级安全认证协议及其研究 3.3.3 重量级安全认证协议及其研究…

Docker 实现MySQL 主从复制

一、拉取镜像 docker pull mysql:5.7相关命令&#xff1a; 查看镜像&#xff1a;docker images 二、启动镜像 启动mysql01、02容器&#xff1a; docker run -d -p 3310:3306 -v /root/mysql/node-1/config:/etc/mysql/ -v /root/mysql/node-1/data:/var/lib/mysql -e MYS…