概念
命令模式(Command Pattern)是一种行为型设计模式,它将请求封装成一个对象,从而允许用不同的请求对客户进行参数化,队列或记录请求日志,并支持可撤销的操作
命令模式通常涉及四个核心角色:命令(Command)、接收者(Receiver)、调用者(Invoker)和客户端(Client)
命令(Command)
定义了执行操作的接口
通常包含一个执行操作的方法,以及可能包含一些其他方法用于支持命令的撤销、重做等操作
接收者(Receiver)
实际执行命令操作的对象
接收者包含了具体的业务逻辑,负责实现命令接口定义的操作
调用者(Invoker)
负责调用命令对象来执行请求
调用者通常不直接与接收者交互,而是通过调用命令对象的方法来执行具体的操作
客户端(Client)
创建命令对象,并将命令对象传递给调用者来执行请求
客户端通常不需要知道命令对象的具体实现,只需要知道如何创建命令对象,并将其传递给调用者即可
命令模式的核心思想是将请求封装成一个对象,使得请求的发送者和接收者之间解耦,从而可以灵活地添加、修改和重用命令对象,同时也提供了一种统一的方式来处理请求的执行、撤销和重做等操作
命令模式适用于以下情况:
- 当需要将请求的发送者和接收者之间解耦,并且希望在不同的请求之间进行参数化时,可以使用命令模式
- 当希望支持命令的撤销、重做等操作,并且希望将这些操作封装到命令对象中时,命令模式也是一个很好的选择
举个简单的例子,考虑一个遥控器系统
遥控器可以控制不同的家电设备(如电视、音响等),而命令模式可以将每个控制命令(如打开、关闭、调高音量等)封装成一个命令对象,并将命令对象传递给遥控器来执行具体的控制操作
这样,可以实现遥控器与家电设备之间的解耦,同时也提供了一种统一的方式来处理控制命令的执行、撤销和重做等操作
实现条件
存在命令对象
命令模式适用于存在一组需要被执行的命令,并且希望将这些命令封装成独立的对象的情况
需要将请求者和接收者解耦
命令模式适用于需要将请求者和接收者解耦的情况,即请求者不需要知道接收者的具体实现细节,而是通过命令对象来与接收者进行通信
需要支持撤销和重做操作
命令模式适用于需要支持撤销和重做操作的情况,因为命令对象可以保存执行命令的历史记录,并且可以根据需要进行撤销和重做操作
需要支持命令队列或者日志
命令模式适用于需要支持命令队列或者日志的情况,因为命令对象可以将所有的命令保存在一个队列中,并且可以将执行命令的日志保存下来
优点
解耦请求者和接收者
命令模式将请求封装成命令对象,使得请求者和接收者之间解耦,请求者不需要知道接收者的具体实现细节
容易扩展新的命令
由于命令模式将每个命令封装成独立的对象,因此容易扩展新的命令,只需要创建新的命令对象并实现对应的执行逻辑即可
支持撤销和重做操作
命令模式可以轻松地支持撤销和重做操作,因为每个命令对象都可以保存执行命令的历史记录,并且可以根据需要进行撤销和重做操作
支持命令队列和日志
命令模式可以支持命令队列和日志功能,因为命令对象可以将所有的命令保存在一个队列中,并且可以将执行命令的日志保存下来
降低系统的耦合度
命令模式降低了系统的耦合度,使得请求者和接收者之间的关系更加灵活,易于维护和扩展
缺点
可能导致类爆炸
命令模式可能会导致类爆炸,因为每个命令都需要定义一个独立的命令类,如果命令较多,可能会导致类的数量增加
增加代码复杂度
命令模式可能会增加代码的复杂度,因为需要定义大量的命令类,并且需要正确地组织和管理这些命令类
可能降低性能
命令模式可能会降低系统的性能,因为需要创建和管理大量的命令对象,并且需要保存命令的历史记录,可能会导致内存占用和执行时间增加
实现方式
控制器基类
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
| import { Command } from './SubClass/Command'
class RemoteControl {
constructor() { this.commands = [] this.history = [] }
setCommand(command) { if (!(command instanceof Command)) { throw new Error("传递的参数必须是Command的实例") } this.commands.push(command) }
pressButton() { if (this.commands.length > 0) { const command = this.commands.shift() if ('execute' in command) { command.execute() this.history.push(command) } else { console.log("无法执行命令:缺少execute方法") } } else { console.log("没有可执行的命令") } }
pressUndo() { if (this.history.length > 0) { const command = this.history.pop() if ('undo' in command) { command.undo() } else { console.log("无法撤销命令:缺少undo方法") } } else { console.log("没有可撤销的命令") } } }
export default RemoteControl
|
子类
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
class Command { constructor(device) { if (new.target === Command) { throw new Error("本类不能实例化") } this.device = device }
execute() { throw new Error("execute 方法必须被重写") }
undo() { throw new Error("undo 方法必须被重写") } }
class TurnOnCommand extends Command {
execute() { this.device.turnOn() }
undo() { this.device.turnOff() } }
class TurnOffCommand extends Command {
execute() { this.device.turnOff() }
undo() { this.device.turnOn() } }
export { Command, TurnOnCommand, TurnOffCommand }
class Stereo {
turnOn() { console.log("音响已打开") }
turnOff() { console.log("音响已关闭") } }
export default Stereo
class Television {
turnOn() { console.log("电视已打开") }
turnOff() { console.log("电视已关闭") } }
export default Television
|
怎么使用
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
|
import RemoteControl from '../CommandPattern' import Television from '../SubClass/Television' import Stereo from '../SubClass/Stereo' import { TurnOffCommand, TurnOnCommand } from '../SubClass/Command'
const tv = new Television() const stereo = new Stereo()
const turnOnTvCommand = new TurnOnCommand(tv) const turnOffTvCommand = new TurnOffCommand(tv)
const turnOnStereoCommand = new TurnOnCommand(stereo) const turnOffStereoCommand = new TurnOffCommand(stereo)
const remoteControl = new RemoteControl()
remoteControl.setCommand(turnOnTvCommand) remoteControl.setCommand(turnOffTvCommand) remoteControl.setCommand(turnOnStereoCommand) remoteControl.setCommand(turnOffStereoCommand)
remoteControl.pressButton() remoteControl.pressButton() remoteControl.pressButton() remoteControl.pressButton()
remoteControl.pressUndo() remoteControl.pressUndo() remoteControl.pressUndo() remoteControl.pressUndo()
remoteControl.pressUndo()
|
场景
GUI应用程序
在GUI应用程序中,命令模式常用于实现菜单和工具栏按钮的操作
每个菜单项或按钮都可以关联一个命令对象,当用户点击时,执行与该命令对象关联的操作
撤销和重做功能
命令模式非常适合实现撤销和重做功能
通过记录执行的命令历史,可以轻松地撤销上一步操作,并且可以再次执行已经撤销的操作
多线程请求处理
在多线程环境下,命令模式可以用于将请求封装成独立的命令对象,这样可以安全地将请求发送给不同的线程进行处理
日程安排
在日程安排应用程序中,用户可以添加、编辑和删除日程事件
每个操作可以表示为一个命令对象,以便能够轻松地撤销或重做这些操作
数据库事务
在数据库操作中,命令模式可以用于实现事务管理
每个数据库操作可以表示为一个命令对象,并且可以将多个命令组合成一个事务,以便能够一次性地执行或回滚一系列操作
远程控制
类似于你提到的遥控器系统,远程控制设备也是命令模式的典型应用场景
通过将每个控制命令封装成一个命令对象,并将命令对象传递给远程设备执行具体的控制操作,可以实现设备之间的解耦
源代码