什么是命令模式(Command Pattern)
命令模式将一个请求封装为一个对象,从而允许你使用不同的请求、队列或日志来参数化其他对象。命令模式也支持可撤销的操作。
想象一下你去餐厅点餐:
- 你(Client)决定要点什么菜(请求)。
- 你告诉服务员(Invoker)你的选择。
- 服务员写下订单(Command 对象),订单上包含了菜品信息(请求细节)以及由谁来做(隐式指向厨师 Receiver)。服务员不需要知道怎么做菜。
- 服务员把订单交给后厨(传递 Command)。
- 厨师(Receiver)拿到订单,根据订单内容烹饪菜肴(执行请求)。
在这个过程中,“订单”就是一个命令对象,它解耦了顾客(发出请求)和服务员(触发执行)与厨师(实际执行者)之间的直接联系。服务员只需要知道“执行这个订单”,而不需要关心订单的具体内容和执行细节。
命令模式的核心在于将动作(请求)本身对象化。
核心思想/步骤
- 定义命令接口 (Command): 定义一个接口,通常包含一个 execute() 方法。所有具体的命令类都需要实现这个接口。有时还会包含一个 undo() 方法来支持撤销。
- 创建具体命令 (ConcreteCommand): 实现 Command 接口。它内部通常包含一个对接收者 (Receiver) 对象的引用。execute() 方法的实现会调用 Receiver 对象上的一个或多个操作来完成请求。它也可以存储执行命令所需的参数。
- 定义接收者 (Receiver): 包含执行请求相关的具体业务逻辑。它知道如何执行与请求相关的操作。一个命令对象只对应一个接收者,但一个接收者可以处理多个命令。
- 创建调用者 (Invoker): 持有一个或多个 Command 对象。它不了解命令的具体内容或接收者是谁。当需要执行命令时,调用者只调用 Command 对象的 execute() 方法。调用者可以是按钮、菜单项、或者一个任务队列。
- 客户端 (Client): 负责创建 ConcreteCommand 对象,并设置其 Receiver。然后,将这个 Command 对象配置给 Invoker。
优点
- 解耦调用者和接收者 (Decoupling): 调用者(Invoker)和接收者(Receiver)之间完全解耦。调用者只需要知道如何调用 execute(),不需要知道请求的具体内容和由谁执行。
- 易于扩展新命令 (Extensibility): 添加新的命令非常简单,只需要创建一个新的 ConcreteCommand 类实现 Command 接口即可,无需修改现有代码(符合开闭原则)。
- 命令可以组合 (Composability): 可以将多个命令组合成一个复合命令(Macro Command),实现一系列操作的批处理。
- 支持撤销和重做 (Undo/Redo): 通过在 Command 接口中添加 undo() 方法,并在 ConcreteCommand 中实现相应的反向操作,可以轻松实现撤销和重做功能(通常需要维护一个命令历史栈)。
- 支持队列和日志 (Queueing & Logging): 命令对象可以被存储在队列中,用于异步执行、任务调度或失败重试。命令的执行过程也可以被记录下来。
- 将操作参数化: 可以将命令对象作为参数传递给方法,实现回调或在运行时动态指定行为。
可能的缺点
- 类的数量膨胀: 每个具体的操作都需要一个对应的 ConcreteCommand 类,如果系统中有大量操作,可能会导致类的数量急剧增加。
- 理解成本: 对于非常简单的操作,引入命令模式可能会增加系统的复杂性,使得代码理解起来稍微困难一些。
使用场景
- 当需要将请求的发送者和接收者解耦时。
- 当需要将操作参数化,以便在不同的时间点指定、排队或执行请求时。
- 当需要支持撤销 (Undo)、重做 (Redo) 或事务(一系列操作要么全成功要么全失败)功能时。
- GUI 应用程序: 按钮点击、菜单项选择等触发的操作,可以将每个操作封装成一个 Command 对象,由 UI 元素(Invoker)触发。
- 任务队列系统: 将待执行的任务(如下载、数据处理)封装成 Command 对象放入队列。
- 宏录制与回放: 将用户的操作序列记录为 Command 对象列表,然后按顺序执行以实现回放。
- Android 中的场景:
- 实现撤销/重做: 在文本编辑器、绘图应用或任何需要撤销操作的场景中,将用户的每次修改(输入文字、删除、绘制图形)封装为 Command 对象。
- 网络请求队列: 将每个 API 请求封装成一个 Command 对象,放入队列中按顺序或并发执行,方便管理(如添加重试逻辑)。
- 遥控器应用: 手机 App 作为遥控器,每个按钮(如“开灯”、“关灯”、“调高音量”)对应一个 Command 对象,发送给接收设备(Receiver,如智能灯泡、电视)。
- 复杂操作的封装: 将一个涉及多个步骤或多个对象交互的复杂操作封装在一个 Command 对象中,简化调用者的逻辑。
- 统一处理来自不同触发点的相同操作: 比如一个“保存”操作可能由菜单项、工具栏按钮或快捷键触发,它们都可以关联到同一个 SaveCommand 对象。
UML 图
下面是基于一个简单的“控制电灯开关”示例的命令模式 UML 图:
- Command (Interface): 定义了 execute() 和 undo() 方法。
- Light (Receiver): 知道如何开关灯 (turnOn(), turnOff())。
- LightOnCommand, LightOffCommand (ConcreteCommand): 实现了 Command 接口。它们都持有一个 Light 对象的引用。execute() 调用 light.turnOn() 或 light.turnOff(),undo() 则执行相反的操作。
- RemoteControl (Invoker): 持有一个 Command 对象 (slot)。buttonWasPressed() 调用 slot.execute()。它还维护了一个 undoCommand 来支持简单的撤销。
- Client: 创建 Light、LightOnCommand、LightOffCommand 和 RemoteControl 实例,并将命令设置到遥控器上。
代码示例
在这个例子中:
- Light 是接收者,负责实际开关灯。
- LightOnCommand 和 LightOffCommand 是具体命令,封装了开灯和关灯的请求,并知道如何通过调用 Light 的方法来执行和撤销。
- RemoteControl 是调用者,它只有一个按钮 (buttonWasPressed) 和一个撤销按钮 (undoButtonWasPressed)。它通过 setCommand 方法被配置,然后调用命令的 execute 或 undo。
- main 函数是客户端,负责创建所有对象并将它们连接起来。
- 我们还引入了一个 NoCommand(空对象模式的应用),用于表示没有命令被设置的情况,避免空指针检查。
总结
- 命令模式通过将请求封装成对象,极大地增强了系统的灵活性和可扩展性。
- 它解耦了请求的发送者和接收者,使得可以独立地改变它们。
- 支持队列、日志、撤销/重做等高级功能是其主要优势。
- 在 Android 开发中,对于需要管理操作序列、实现撤销功能或解耦 UI 事件与业务逻辑的场景非常有用。
记住它的核心:请求变对象,调用者只管执行,接收者负责实现。
- Author:CoderWdd
- URL:https://www.wuinsights.top//article/18c5a994-0b2d-4e13-b389-cf56974e811d
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts