Objective-C设计模式解析-命令

看图识模式

前提&场景

我们有若干个军团,军团有若干功能:1.陆地攻击目标 2.轰炸目标 3.保护目标 4.从目标撤退等。
将军可以下达指令给军团,调用军团的功能。

需求

在一场battle中,将军运筹帷幄,给旗下的军团下达了一系列的命令:
攻击1号山地
攻击4号山地
保护3号山地
……

如果用类去实现,可以这样

《Objective-C设计模式解析-命令》

将军直接调用军团A的attack:方法,同样也可以直接调用bomb、protect方法,这样并没有问题。

如果我们引入新的需求:

我们需要对将军下发的命令进行排队(下发多条命令时需要按照优先级执行)、记录执行命令、撤销命令、传递命令、重复执行等相关操作。

如果还是按照上面直接调用来做,基本上很难实现,有以下几个主要问题:

  • 不同军种攻击方法不同,需要知道每个类或方法的细节才能调用。导致彼此拥有高耦合度。

    • 将军下命令攻击某个标的,本质上他并不关心由那个军种该如何攻击的这种细节。
  • 对命令进行操作(撤销、记录等)将非常复杂

    • 直接调用的后果就是不能直接找到某次调用,再对这次操作进行操作。通常需要额外的环境存放这些操作相关的参数、顺序、执行者等,当程序变大时,基本上难以维护。
  • 命令不可传递和复用

    • 如果需要多个军团攻击目标A,需要调用每个军团攻击方法。某个时间重复攻击,任然需要再次调用,不能复用。

如何设计呢?

这里主要解决2个问题
  • 需要把命令封装起来,这样我们就可以对这个封装的对象进行种种操作了

    比如将军下的命令是一封信,信里上标明执行的等级,攻击的目标等参数信息。
    当若干封信来的时候我们可以根据等级排列,执行完后还可以把信交给其它军团再次执行。。。
  • 调用者和执行者解耦

    将军(调用者)封装一个命令对象,哪个军团接到这个命令对象就执行这个命令,调用者不用关心执行的细节,只需关注命令本身即可。

如下图:

《Objective-C设计模式解析-命令》

模式定义

定义

命令模式: 将请求封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

静态类图

《Objective-C设计模式解析-命令》

命令模式包含如下角色:
  • 客户端(Client)角色: 创建一个具体命令(ConcreteCommand)对象并确定其接收者。
  • 命令(Command)角色:声明了一个给所有具体命令类的抽象接口。
  • 具体命令(ConcreteCommand)角色:定义一个接收者和行为之间的弱耦合;实现execute()方法,负责调用接收者的相应操作。execute()方法通常叫做执行方法。
  • 请求者(Invoker)角色:负责调用命令对象执行请求,相关的方法叫做行动方法。
  • 接收者(Receiver)角色:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。

分析

命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。

  • 每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。
  • 命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
  • 命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
  • 命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。

动机

在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。

命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。

Command模式是最让我疑惑的一个模式。它实际上不是个很具体,规定很多的模式,正是这个灵活性,让人容易产生confuse。

代码

根据上面的场景来进行code实现;

接收者

接受者是命令模式中去执行具体操作的对象,这里指的具体军团

@interface Legion : NSObject

@property (nonatomic, copy) NSString *name;

- (instancetype)initWithName: (NSString *)name;

// 攻击
- (void)attack:(NSString *)target;
// 轰炸
- (void)bomb:(NSString *)target;
// 保护
- (void)protect:(NSString *)target;
// 撤回
- (void)recall:(NSString *)target;

@end

@implementation Legion

- (instancetype)initWithName: (NSString *)name
{
    self = [super init];
    if (self) {
        _name = name;
    }
    return self;
}

- (void)attack:(NSString *)target
{
    NSLog(@"军团: %@--------攻击: %@", self.name, target);
}

- (void)bomb:(NSString *)target
{
    NSLog(@"军团: %@--------轰炸: %@", self.name, target);
}

- (void)protect:(NSString *)target
{
    NSLog(@"军团: %@--------保护: %@", self.name, target);
}

- (void)recall:(NSString *)target
{
    NSLog(@"军团: %@--------撤回: %@", self.name, target);
}

@end

抽象命令Command

命令对象中暴露了指定的调用execute方法,命令的调用者和接收者针对抽象的命令类编写,从而达到解耦合的目的。
发起命令的对象完全不知道具体实现对象是谁,也不知道如何实现。

#import "Legion.h"

@interface Command : NSObject

@property (nonatomic, assign) NSInteger level;
@property (nonatomic, strong) Legion *legion;
@property (nonatomic, copy) NSString *target;

- (instancetype)initWithReciver:(Legion *)legion target:(NSString *)target;

- (void)attack;

- (void)recall;

@end

@implementation Command

- (instancetype)initWithReciver:(Legion *)legion target:(NSString *)target
{
    self = [super init];
    if (self) {
        _legion = legion;
        _target = target;
    }
    return self;
}

- (void)attack
{
    
}

- (void)recall
{
    
}

@end

具体命令

陆地攻击命令 LandAttackCommand
#import "Command.h"

@interface LandAttackCommand : Command

- (void)attack;
- (void)recall;

@end

@implementation LandAttackCommand

- (void)attack
{
    [self.legion attack:self.target];
}

- (void)recall
{
    [self.legion recall:self.target];
}

@end
空袭命令 AirAttackCommand
#import "Command.h"

@interface AirAttackCommand : Command

- (void)attack;
- (void)recall;

@end

@implementation AirAttackCommand

- (void)attack
{
    [self.legion bomb:self.target];
}

- (void)recall
{
    [self.legion recall:self.target];
}

@end

调用者&客户端

这里我们可以写一个调用者类,用这个类负责命令的创建和调用,还有对命令的管理(比如排序、日志记录等等)。
这里我就不单独写了,具体业务中可以依据具体情况去编写。
下面的调用例子把客户端和调用者放在一起不做具体区分。

        Legion *legionA = [[Legion alloc] initWithName:@"军团A"];
        
        Command *landComand1 = [[LandAttackCommand alloc] initWithReciver:legionA target:@"4号高地"];
        landComand1.level = 100;
        Command *landComand2 = [[LandAttackCommand alloc] initWithReciver:legionA target:@"5号高地"];
        landComand2.level = 200;
        Command *airAttackCommand1 = [[AirAttackCommand alloc] initWithReciver:legionA target:@"4号高地"];
        airAttackCommand1.level = 50;
        Command *airAttackCommand2 = [[AirAttackCommand alloc] initWithReciver:legionA target:@"5号高地"];
        airAttackCommand2.level = 500;
        
        NSArray *commandList = @[landComand1, landComand2, airAttackCommand1, airAttackCommand2];
        for (Command *comand in commandList) {
            [comand attack];
        }
        
        // 命令传递
        Legion *legionB = [[Legion alloc] initWithName:@"军团B"];
        landComand1.legion = legionB;
        [landComand1 attack];
        
        // 撤销
        [landComand1 recall];
        
        // 可以根据level等级对所有的对命令进行排序、记录等操作...

结果

《Objective-C设计模式解析-命令》

由此可见,我们对请求行为进行了封装,从而可以对这个请求对象(命令)进行一系列的操作。而且还化解了调用者和接收者之间的耦合关系。

特征

使用环境

在以下情况下可以使用命令模式:

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  • 系统需要在不同的时间指定请求、将请求排队和执行请求。
  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
  • 系统需要将一组操作组合在一起,即支持宏命令

优点&缺点

优点

命令模式的优点

  • 降低系统的耦合度。
  • 新的命令可以很容易地加入到系统中。
  • 可以比较容易地设计一个命令队列和宏命令(组合命令)。
  • 可以方便地实现对请求的Undo和Redo。
缺点

命令模式的缺点

  • 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
    原文作者:设计模式
    原文地址: https://segmentfault.com/a/1190000012256637
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞