命令模式
发表于更新于
设计模式命令模式
Rif这个模式的核心思想是“请求的封装”,它在实现撤销/重做、任务队列、事务操作等方面发挥着至关重要的作用。
设计模式笔记:命令模式 (Command Pattern)
一、一句话概括
将一个请求封装成一个对象,从而可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
二、为什么需要它 (Why - The Pain Point)
想象一下,你正在设计一个智能家居遥控器。这个遥控器上有很多按钮,每个按钮可以控制不同的设备(灯、风扇、电视等)执行不同的操作(打开、关闭、调高音量等)。
直接实现的糟糕设计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Light { public void on() { ... } public void off() { ... } } class Fan { public void high() { ... } public void off() { ... } }
class RemoteControl { private Light livingRoomLight; private Fan ceilingFan;
public void onButton1Pressed() { livingRoomLight.on(); } public void offButton1Pressed() { livingRoomLight.off(); } public void onButton2Pressed() { ceilingFan.high(); } }
|
痛点:请求者与接收者的紧密耦合
- 高耦合:
RemoteControl(请求者)必须直接了解并持有所有设备对象(接收者)的引用,并且知道每个设备具体的方法名(on(), high() 等)。
- 违反开闭原则:如果要增加一个新的设备(比如音响
Stereo),或者想改变一个按钮的功能(比如让按钮1控制风扇),就必须修改 RemoteControl 的源代码。
- 难以实现高级功能:
- **撤销/重做 (Undo/Redo)**:如何记录上一步操作并撤销它?比如按了“开灯”后,如何实现一个“撤销”按钮来关灯?
- 任务队列:如何将一系列操作(先开灯、再开风扇、再开电视)组合成一个“宏命令”,按一个按钮就全部执行?
- 日志记录:如何记录所有执行过的操作以便后续审计或重放?
核心问题:如何将“发出请求的对象”和“知道如何执行请求的对象”完全解耦,并将“请求”本身变为一个可操作、可传递、可存储的实体?
三、它是什么 (What - The Solution)
命令模式通过引入命令对象(Command Object) 来解耦这一切。命令对象封装了关于一个操作的所有信息:它应该调用哪个对象的哪个方法,以及需要哪些参数。
核心思想和角色:
- **命令接口 (Command)**:
- 通常只声明一个核心方法,如
execute()。
- (可选)为了支持撤销,可以再声明一个
undo() 方法。
- **具体命令 (Concrete Command)**:
- 实现了
Command 接口。
- 它持有一个接收者(Receiver)对象的引用。
execute() 方法的实现就是调用接收者的一个或多个具体方法来完成操作。
undo() 方法的实现则是调用接收者的方法来执行与 execute() 相反的操作。
- **接收者 (Receiver)**:
- 这是真正执行业务逻辑的对象。它知道如何完成具体的工作。
- 例如,
Light, Fan 等设备类。
- **调用者 (Invoker)**:
- 它负责触发命令。它持有一个或多个命令对象。
- 它不关心命令的具体内容,只知道在适当的时候调用命令的
execute() 方法。
- 例如,遥控器上的按钮、菜单项等。
- **客户端 (Client)**:
- 客户端负责创建具体命令对象,并将其接收者设置好。
- 然后,客户端将这个配置好的命令对象设置给调用者。
工作流程:
- 客户端创建了一个
Light 对象(接收者)和一个 LightOnCommand 对象(具体命令),并将 Light 对象注入到 LightOnCommand 中。
- 客户端将
LightOnCommand 设置到遥控器的一个按钮(调用者)上。
- 当用户按下该按钮时,调用者(按钮)只管调用它持有的命令对象的
execute() 方法。
LightOnCommand 的 execute() 方法被触发,它进而调用内部持有的 Light 对象的 on() 方法。
现在,遥控器完全不知道灯的存在,它只认识抽象的 Command 接口。
四、怎么做 (How - The Blueprint)
基本步骤(以智能遥控器为例):
- 创建接收者类,如
Light, Fan,包含具体的业务方法。
- 创建
Command 接口,包含 execute() 和 undo() 方法。
- 创建具体命令类,如
LightOnCommand。它持有 Light 的引用,并实现 execute()(调用 light.on())和 undo()(调用 light.off())。
- **创建调用者类
RemoteControl**。它内部可以有一个 Command 数组来对应每个按钮,并提供 setCommand() 方法。当按钮被按下时,调用对应命令的 execute()。
- 客户端负责组装这一切。
Java 示例代码 (支持撤销)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| class Light { private 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"); } }
interface Command { void execute(); void undo(); }
class LightOnCommand implements Command { private Light light;
public LightOnCommand(Light light) { this.light = light; }
@Override public void execute() { light.on(); }
@Override public void undo() { light.off(); } }
class LightOffCommand implements Command { private Light light; public LightOffCommand(Light light) { this.light = light; } @Override public void execute() { light.off(); } @Override public void undo() { light.on(); } }
class NoCommand implements Command { public void execute() {} public void undo() {} }
class RemoteControl { private Command[] onCommands; private Command[] offCommands; private Command undoCommand;
public RemoteControl() { onCommands = new Command[7]; offCommands = new Command[7]; Command noCommand = new NoCommand(); for (int i = 0; i < 7; i++) { onCommands[i] = noCommand; offCommands[i] = noCommand; } undoCommand = noCommand; }
public void setCommand(int slot, Command onCommand, Command offCommand) { onCommands[slot] = onCommand; offCommands[slot] = offCommand; }
public void onButtonWasPushed(int slot) { onCommands[slot].execute(); undoCommand = onCommands[slot]; }
public void offButtonWasPushed(int slot) { offCommands[slot].execute(); undoCommand = offCommands[slot]; }
public void undoButtonWasPushed() { System.out.print("Undo: "); undoCommand.undo(); } }
public class CommandPatternDemo { public static void main(String[] args) { RemoteControl remote = new RemoteControl();
Light livingRoomLight = new Light("Living Room"); LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight); LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
remote.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remote.onButtonWasPushed(0); remote.offButtonWasPushed(0); System.out.println("--- Testing Undo ---"); remote.onButtonWasPushed(0); remote.undoButtonWasPushed(); remote.offButtonWasPushed(0); remote.undoButtonWasPushed(); } }
|
五、UML 类图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| @startuml skinparam classAttributeIconSize 0 hide empty members
' Command Interface interface Command { + void execute() + void undo() }
' Concrete Commands class LightOnCommand implements Command { - Light light + void execute() + void undo() } class LightOffCommand implements Command { - Light light + void execute() + void undo() } class NoCommand implements Command
' Receiver class Light { + void on() + void off() }
' Invoker class RemoteControl { - Command[] onCommands - Command[] offCommands - Command undoCommand + void setCommand(...) + void onButtonWasPushed(int slot) + void undoButtonWasPushed() }
' Client class CommandPatternDemo {}
' Relationships ' Invoker HAS-A Command RemoteControl o-- "1..*" Command
' ConcreteCommand HAS-A Receiver LightOnCommand o-- "1" Light LightOffCommand o-- "1" Light
' Client creates Receiver, Command and configures Invoker CommandPatternDemo ..> RemoteControl CommandPatternDemo ..> Light CommandPatternDemo ..> LightOnCommand @enduml
|
图示解读:
RemoteControl (调用者) 拥有 Command 对象。
LightOnCommand (具体命令) 拥有 Light 对象 (接收者)。
- 调用者和接收者之间没有直接联系,通过命令对象解耦。
六、优缺点分析
优点 (Pros):
- 解耦:将请求的发送者(调用者)和执行者(接收者)完全解耦。
- 可扩展性:增加新的命令和接收者非常容易,只需创建新的具体命令类,无需修改现有代码,符合开闭原则。
- 请求可被操作:将请求封装成对象后,可以像操作其他对象一样操作它,比如将其存入队列、日志、传递等。
- 支持高级功能:很容易实现撤销/重做、宏命令(将多个命令组合成一个复合命令)、事务等复杂功能。
缺点 (Cons):
- 类数量增加:对于每一个操作,都可能需要创建一个具体的命令类,这会导致系统中类的数量增多。
- 优化:对于简单的命令,可以使用 Lambda 表达式 来代替具体的命令类,极大地简化代码。在 Java 8+ 中,
() -> light.on() 就是一个天然的命令对象。
七、在 Java 中的应用
java.lang.Runnable: Runnable 接口是命令模式的完美体现。
- Command:
Runnable 接口,只有一个 run() 方法(相当于 execute())。
- Concrete Command: 任何实现了
Runnable 的类,或者一个 Lambda 表达式。
- Receiver:
run() 方法内部调用的任何对象。
- Invoker:
Thread 类。new Thread(myRunnable).start() 就是调用者在执行命令。
- Java Swing / AWT 中的
ActionListener:
- Command:
ActionListener 接口,只有一个 actionPerformed() 方法。
- Invoker:
JButton 等组件。
- 当你调用
button.addActionListener(...) 时,就是在设置命令。
- 数据库事务:可以将一系列数据库操作(Insert, Update, Delete)封装成命令对象,放入一个队列中。然后可以按顺序执行它们,如果中途出错,可以反向调用每个已执行命令的
undo() 方法,实现事务回滚。