前言
什么是命令模式
在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,实现二者之间的松耦合。这就是命令模式(Command Pattern)
模式结构
Command:
定义命令的接口,声明执行的方法。
ConcreteCommand:
命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
Receiver:
接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
Invoker:
要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
Client:
创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。
模式分析
- 命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。
- 每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。
- 命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
- 命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
- 命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。
代码
场景
在当前控制器中心创建一个红色view,增加三个按钮,分别是放大,缩小,回退。对应的红色view就会放大,缩小,回退。
ViewController
创建ViewController类,显示redView,并有三个按钮
#import "ViewController.h"
#import "Receiver.h"
#import "reduceCommand.h"
#import "Invoker.h"
#import "amplifyCommand.h"
#import "CommandProtocol.h"
#define kccWidth [UIScreen mainScreen].bounds.size.width;
@interface ViewController ()
@property (strong, nonatomic) UIView * redView;
@property (strong, nonatomic) Receiver * receiver;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
CGPoint centerPoint = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2);
self.redView = [[UIView alloc]initWithFrame:CGRectMake(centerPoint.x - 50/2,centerPoint.y - 50/2, 50, 50)];
self.redView.backgroundColor = [UIColor redColor];
[self.view addSubview:self.redView];
self.receiver = [[Receiver alloc]init];
self.receiver.redView = self.redView;
self.receiver.width = 50;
}
/// 扩大
/// @param sender 扩大按钮
- (IBAction)amplify:(UIButton *)sender {
amplifyCommand *command = [[amplifyCommand alloc]initWithReceiver:self.receiver paramter:10];
[[Invoker sharedInstance] addAndExcute:command];
}
/// 缩小
/// @param sender 缩小按钮
- (IBAction)reduce:(UIButton *)sender {
reduceCommand *command = [[reduceCommand alloc]initWithReceiver:self.receiver paramter:10];
[[Invoker sharedInstance] addAndExcute:command];
}
/// 回退
/// @param sender 回退按钮
- (IBAction)rollback:(UIButton *)sender {
// 回退操作
[[Invoker sharedInstance] rollBack];
}
@end
接收者Receiver
负责具体实现的细节
#import <UIKit/UIKit.h>
@interface Receiver : NSObject
@property (assign, nonatomic) CGFloat width;
@property (strong, nonatomic) UIView * redView;
/// 扩大view
/// @param pamameter 边长
-(void)amplifyView:(CGFloat)pamameter;
/// 缩小view
/// @param pamameter 边长
-(void)reduceView:(CGFloat)pamameter;
@end
#import "Receiver.h"
#define centerX [UIScreen mainScreen].bounds.size.width/2
#define centerY [UIScreen mainScreen].bounds.size.height/2
@implementation Receiver
-(void)amplifyView:(CGFloat)pamameter{
_width += pamameter;
_width = MIN(_width, [UIScreen mainScreen].bounds.size.width);
_redView.frame = CGRectMake(centerX - _width/2,centerY - _width/2, _width, _width);
}
-(void)reduceView:(CGFloat)pamameter{
_width -= pamameter;
_width = MAX(_width, 30);
_redView.frame = CGRectMake(centerX - _width/2,centerY - _width/2, _width, _width);
}
@end
接口命令CommandProtocol
#import <Foundation/Foundation.h>
@protocol CommandProtocol <NSObject>
/// 执行命令
- (void)excute;
/// 撤销命令
- (void)backExcute;
@end
具体的命令
amplifyCommand 放大
#import <Foundation/Foundation.h>
#import "CommandProtocol.h"
#import "Receiver.h"
@interface amplifyCommand : NSObject<CommandProtocol>
// 绑定接收器
- (instancetype)initWithReceiver:(Receiver *)receiver paramter:(CGFloat)paramter;
@end
#import "amplifyCommand.h"
@interface amplifyCommand()
@property (nonatomic, strong) Receiver *receiver;
@property (nonatomic, assign) CGFloat paramter;
@end
@implementation amplifyCommand
- (instancetype)initWithReceiver:(Receiver *)receiver paramter:(CGFloat)paramter{
self = [super init];
if (self) {
self.receiver = receiver;
self.paramter = paramter;
}
return self;
}
// 执行命令
- (void)excute {
[self.receiver amplifyView:self.paramter];
}
// 撤销命令
- (void)backExcute {
[self.receiver reduceView:self.paramter];
}
@end
reduceCommand 缩小
#import <Foundation/Foundation.h>
#import "CommandProtocol.h"
#import "Receiver.h"
@interface reduceCommand : NSObject<CommandProtocol>
// 绑定接收器
- (instancetype)initWithReceiver:(Receiver *)receiver paramter:(CGFloat)paramter;
@end
#import "reduceCommand.h"
@interface reduceCommand()
@property (nonatomic, strong) Receiver *receiver;
@property (nonatomic, assign) CGFloat paramter;
@end
@implementation reduceCommand
- (instancetype)initWithReceiver:(Receiver *)receiver paramter:(CGFloat)paramter{
self = [super init];
if (self) {
self.receiver = receiver;
self.paramter = paramter;
}
return self;
}
// 执行命令
- (void)excute {
[self.receiver reduceView:self.paramter];
}
// 撤销命令
- (void)backExcute {
[self.receiver amplifyView:self.paramter];
}
@end
请求者 Receiver
#import <Foundation/Foundation.h>
#import "CommandProtocol.h"
@interface Invoker : NSObject
+ (instancetype)sharedInstance;
// 回退指令
- (void)rollBack;
// 添加操作指令
- (void)addAndExcute:(id <CommandProtocol>)command;
@end
#import "Invoker.h"
@interface Invoker()
@property (strong, nonatomic) NSMutableArray *mArr; //存储操作指令的数组
@end
@implementation Invoker
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
static Invoker *cls = nil;
dispatch_once(&onceToken, ^{
cls = [[[self class] alloc] init];
cls.mArr = [[NSMutableArray alloc]init];
});
return cls;
}
- (void)rollBack{
// 1.获取数组中的最后一个操作
id <CommandProtocol> command = self.mArr.lastObject;
// 2.操作调用,撤销的步骤
[command backExcute];
// 3.删除最后操作
[self.mArr removeLastObject];
}
// 添加操作指令
- (void)addAndExcute:(id <CommandProtocol>)command {
// 1.把操作添加到数组
[self.mArr addObject:command];
// 2.让操作调用实现的协议方法
[command excute];
}
@end
模式优点
- 降低对象之间的耦合度。
- 新的命令可以很容易地加入到系统中。
- 可以比较容易地设计一个组合命令。
- 调用同一方法实现不同的功能
模式缺点
- 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。