1. 命令模式概述
在软件开发中, 我们经常会在一个类中调用另一个类的方法, 简单地说, 就是给另一个类发送一条命令, 这时候, 这两个类之间耦合性是非常高的, 任何一方的任何变化都会引起另一个的变化, 为了解决这个问题, 引入了命令模式的概念, 简单说, 命令模式就是在这两个类之间引入一个抽象命令类, 命令的发送者针对抽象命令类编程, 这时候, 命令的发送者并不需要知道命令接收者到底是谁, 也不需要知道指定会被怎样执行, 只需要简单的发送出去即可; 实际命令类包含对命令接收者的引用, 当命令发送者调用发送命令时候, 实际命令类负责转发命令给命令接收者, 命令接收者负责具体的操作.(以上纯属个人见解)
命令模式可以将请求发送者和接收者完全解耦, 命令发送者和接收者之间没有直接引用关系, 发送请求的对象只需要知道如何发送请求, 而不必知道如何完成请求.
命令模式: 将请求封装成一个对象, 从而可用不同的请求对客户进行参数化; 对请求队列或者记录请求日志, 以及支持可撤销的操作. 命令模式是一种行为型模式, 其别名是动作模式或事务模式.
命令模式的核心在于引入了命令类, 通过命令类来降低发送者和接收者的耦合度, 请求发送者只需指定一个命令对象, 再通过命令对象来调用请求接收者的处理方法.
在命令模式结构图中包含如下几个角色:
- Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作。
- ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现execute()方法时,将调用接收者对象的相关操作(Action)。
- Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。
- Receiver(接收者):接收者执行与请求相关的操作,它具体实现对请求的业务处理。
命令模式的本质是对请求进行封装, 一个请求对应一个命令, 将发出命令的责任和执行命令的责任分隔开. 每一个命令都是一个操作: 请求的一方发出请求要求执行一个操作; 接收的一方收到请求, 并执行相应的操作. 命令模式允许请求的一方和接收的一方单独开来, ?请求的一方必须知道接收请求的一方的接口, 更不必知道请求如何被接收, 操作是否被执行, 何时被执行, 以及怎么被执行的.
命令模式的关键在于引入了抽象命令类, 请求发送者针对抽象命令类编程, 只有实现了抽象命令类具体命令才与请求接收者相关联.
2. 命令模式完整的swift实现
Swift版命令模式类图:
// 命令发送者类class Invoker: NSObject { var command: Command init(command: Command) { self.command = command } func onClick() { command.execute() }}// 抽象命令类, 定义执行命令的方法protocol Command { func execute();}// 最小化窗口的具体命令类class MiniWindowCommand: NSObject, Command { var handler: ReceiveA init(handler: ReceiveA) { self.handler = handler } func execute() { handler.miniSize() }}// 展示帮助详情信息的命令类class HelpCommand: NSObject, Command { var handler: ReceiveB init(handler: ReceiveB) { self.handler = handler } func execute() { self.handler.display() }}// 命令接收者class ReceiveA: NSObject { func miniSize() { print("窗口被最小化") }}// 命令接收者class ReceiveB: NSObject { func display() { print("展示帮助信息") }}
客户端代码以及结果
3. 命令队列的实现
有时候我们需要将多个请求排队, 当一个请求发送者发送一个请求时, 将不止一个请求接收者产应响应, 这些请求接收者将逐个执行业务方法, 完成请求的处理. 此时我们可以通过请求队列来实现.
命令队列的实现方法有多重, 最常用,最灵活最好的一种方式是增减一个增加一个CommandQueue类, 由该类负责存储多个命令对象, 而不同的命令对象可以对应不同的请求接收者.
简单的说, 命令队列就是在抽象命令类和命令发送者之间加入一个CommandQueue对象来统一管理所有的命令类, 接收来自命令发送者的命令, 转发命令到所有命令接收者之中.(本段为个人理解)
Swift版本代码实现如下:
// swift中的数组, 当前还不能直接从数组中删除元素, 所以这里使用NSMutableArray.class CommandQueue: NSObject, Command { lazy var commandQueue = NSMutableArray() func addCommand(command: Command) { commandQueue.add(command) } func removeCommand(command: Command) { commandQueue.remove(command) } func execute() { for command in commandQueue { (command as! Command).execute() } }}
4. 宏命令
宏命令又称为组合命令, 它是组合模式和命令模式联合的产物. 宏命令是一个具体的命令类, 她拥有一个集合属性, 在该集合中包含了对其他命令对象的引用. 通常宏命令不直接与请求接收者交互, 而是通过它的成员来调用接收者的方法, 当调用宏命令的execute()方法时 ,将递归调用它所包含的每个成员命令的execute方法, 一个宏命令的成员可以是简单命令, 还可以继续是宏命令, 执行一个宏命令将处罚多个具体命令的执行, 从而实现对命令的批处理.
首先, 宏命令, 听这名字叼的一逼, 但是仔细分析后发现, 其实就那么回事, 如果命令队列实现了抽象命令类其实就可以看成是一个宏命令, 一个宏命令类可以包含其它具体宏命令类和具体命令类, 因为都遵循抽象类嘛! 不过这名字还是可以的.
5. 命令模式总结
命令模式是一种使用频率非常高的设计模式,它可以将请求发送者与接收者解耦,请求发送者通过命令对象来间接引用请求接收者,使得系统具有更好的灵活性和可扩展性。在基于GUI的软件开发,无论是在电脑桌面应用还是在移动应用中,命令模式都得到了广泛的应用。
主要优点
- 降低系统的耦合度。由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦,相同的请求者可以对应不同的接收者,同样,相同的接收者也可以供不同的请求者使用,两者之间具有良好的独立性。
- 新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的要求。
- 可以比较容易地设计一个命令队列或宏命令(组合命令)。
- 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案。
主要缺点
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。
适用场景
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用。
- 系统需要在不同的时间指定请求、将请求排队和执行请求。一个命令对象和请求的初始调用者可以有不同的生命期,换言之,最初的请求发出者可能已经不在了,而命令对象本身仍然是活动的,可以通过该命令对象去调用请求接收者,而无须关心请求调用者的存在性,可以通过请求日志文件等机制来具体实现。
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
- 系统需要将一组操作组合在一起形成宏命令。
更多详情请参考原文:
Reference: