Objective-C設計模式解析-命令

看圖識模式

前提&場景

咱們有若干個軍團,軍團有若干功能:1.陸地攻擊目標 2.轟炸目標 3.保護目標 4.從目標撤退等。
將軍能夠下達指令給軍團,調用軍團的功能。編程

需求

在一場battle中,將軍指揮若定,給旗下的軍團下達了一系列的命令:
攻擊1號山地
攻擊4號山地
保護3號山地
......atom

若是用類去實現,能夠這樣spa

clipboard.png

將軍直接調用軍團A的attack:方法,一樣也能夠直接調用bomb、protect方法,這樣並無問題。設計

若是咱們引入新的需求:3d

咱們須要對將軍下發的命令進行排隊(下發多條命令時須要按照優先級執行)、記錄執行命令、撤銷命令、傳遞命令、重複執行等相關操做。日誌

若是仍是按照上面直接調用來作,基本上很難實現,有如下幾個主要問題:code

  • 不一樣軍種攻擊方法不一樣,須要知道每一個類或方法的細節才能調用。致使彼此擁有高耦合度。對象

    • 將軍下命令攻擊某個標的,本質上他並不關心由那個軍種該如何攻擊的這種細節。
  • 對命令進行操做(撤銷、記錄等)將很是複雜blog

    • 直接調用的後果就是不能直接找到某次調用,再對此次操做進行操做。一般須要額外的環境存放這些操做相關的參數、順序、執行者等,當程序變大時,基本上難以維護。
  • 命令不可傳遞和複用排序

    • 若是須要多個軍團攻擊目標A,須要調用每一個軍團攻擊方法。某個時間重複攻擊,任然須要再次調用,不能複用。

如何設計呢?

這裏主要解決2個問題
  • 須要把命令封裝起來,這樣咱們就能夠對這個封裝的對象進行種種操做了

    好比將軍下的命令是一封信,信裏上標明執行的等級,攻擊的目標等參數信息。
    當若干封信來的時候咱們能夠根據等級排列,執行完後還能夠把信交給其它軍團再次執行。。。
  • 調用者和執行者解耦

    將軍(調用者)封裝一個命令對象,哪一個軍團接到這個命令對象就執行這個命令,調用者不用關心執行的細節,只需關注命令自己便可。

以下圖:

clipboard.png

模式定義

定義

命令模式: 將請求封裝爲一個對象,從而可用不一樣的請求對客戶進行參數化,對請求排隊或記錄請求日誌,以及支持可撤銷的操做。

靜態類圖

clipboard.png

命令模式包含以下角色:
  • 客戶端(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等級對全部的對命令進行排序、記錄等操做...

結果

clipboard.png

因而可知,咱們對請求行爲進行了封裝,從而能夠對這個請求對象(命令)進行一系列的操做。並且還化解了調用者和接收者之間的耦合關係。

特徵

使用環境

在如下狀況下可使用命令模式:

  • 系統須要將請求調用者和請求接收者解耦,使得調用者和接收者不直接交互。
  • 系統須要在不一樣的時間指定請求、將請求排隊和執行請求。
  • 系統須要支持命令的撤銷(Undo)操做和恢復(Redo)操做。
  • 系統須要將一組操做組合在一塊兒,即支持宏命令

優勢&缺點

優勢

命令模式的優勢

  • 下降系統的耦合度。
  • 新的命令能夠很容易地加入到系統中。
  • 能夠比較容易地設計一個命令隊列和宏命令(組合命令)。
  • 能夠方便地實現對請求的Undo和Redo。
缺點

命令模式的缺點

  • 使用命令模式可能會致使某些系統有過多的具體命令類。由於針對每個命令都須要設計一個具體命令類,所以某些系統可能須要大量具體命令類,這將影響命令模式的使用。
相關文章
相關標籤/搜索