面向對象設計的設計模式(三):行爲型模式(附 Demo & UML類圖)

本篇是面向對象設計系列文章的第四篇,講解的是設計模式中的7個比較常見的行爲型模式(按照本文講解順序排列):java

  • 模板方法模式
  • 策略模式
  • 責任鏈模式
  • 狀態模式
  • 命令模式
  • 觀察者模式
  • 中介者模式

一. 模板方法模式

定義

在模板模式(Template Method Pattern)中,定義一個操做中的算法的框架,而將一些步驟的執行延遲到子類中,使得子類能夠在不改變算法的結構的前提下便可從新定義該算法的某些特定步驟。node

適用場景

一般一個算法須要幾個執行步驟來實現,而有時咱們須要定義幾種執行步驟一致,可是卻可能在某個步驟的實現略有差別的算法。也就是說咱們既須要複用實現相同的步驟,也能夠經過在某個步驟的不一樣實現來靈活擴展出更多不一樣的算法。git

在這種場景下,咱們可使用模板方法模式:定義好一個算法的框架,在父類實現能夠複用的算法步驟,而將須要擴展和修改其餘步驟的任務推遲給子類進行。程序員

如今咱們清楚了模板方法模式的適用場景,下面看一下這個模式的成員和類圖。github

成員與類圖

成員

模板方法模式的成員除了客戶端之外,只有兩個成員:算法

  • 算法類(Algorithm):算法類負責聲明算法接口,算法步驟接口。並實現可複用的算法步驟接口,且將須要子類實現的接口暴露出來。
  • 具體算法類(Concrete Algorithm):具體算法類負責實現算法類聲明的算法步驟接口。

有些參考資料定義這兩個成員爲Abstract ClassConcrete Class編程

下面經過類圖來看一下命令模式各個成員之間的關係:設計模式

模式類圖

模板方法模式類圖

由上圖能夠看出,Algorithmexcute方法是算法接口,它在內部調用了三個步驟方法:step1,step2,step3。而step2是未暴露在外部的,由於這個步驟是須要各個子類複用的。所以Algorithm只將step1step3暴露了出來以供子類來調用。數組

代碼示例

場景概述

模擬一個製做三種熱飲的場景:熱美式咖啡,熱拿鐵,熱茶。安全

場景分析

這三種熱飲的製做步驟是一致的,都是三個步驟:

  • 步驟一:準備熱水
  • 步驟二:加入主成分
  • 步驟三:加入輔助成分(也能夠不加,看具體熱飲的種類)

雖然製做步驟是一致的,可是不一樣種類的熱飲在每一步多是不一樣的:咖啡和茶葉主成分是咖啡粉和茶葉;而輔助成分:美式咖啡和茶葉能夠不添加,而拿鐵還需添加牛奶。

而第一步是相同的:準備熱水。

根據上面對模板方法模式的介紹,像這樣算法步驟相同,算法步驟裏的實現可能相同或不一樣的場景咱們可使用模板方法模式。下面咱們看一下如何用代碼來模擬該場景。

代碼實現

首先咱們建立算法類HotDrink

//================== HotDrink.h ==================

@interface HotDrink : NSObject

- (void)makingProcess;

- (void)addMainMaterial;

- (void)addIngredients;

@end



//================== HotDrink.m ==================

@implementation HotDrink

- (void)makingProcess{
    
    NSLog(@" ===== Begin to making %@ ===== ", NSStringFromClass([self class]));
    
    [self boilWater];
    [self addMainMaterial];
    [self addIngredients];
}


- (void)prepareHotWater{
    
    NSLog(@"prepare hot water");
}


- (void)addMainMaterial{
    
    NSLog(@"implemetation by subClasses");
}


- (void)addIngredients{
    
    NSLog(@"implemetation by subClasses");
}


@end
複製代碼

HotDrink向外部暴露了一個製做過程的接口makingProcess,這個接口內部調用了熱飲的全部製做步驟方法:

- (void)makingProcess{
         
     //準備熱水 
    [self prepareHotWater];
    
    //添加主成分
    [self addMainMaterial];
    
    //添加輔助成分
    [self addIngredients];
}
複製代碼

HotDrink只向外暴露了這三個步驟中的兩個須要子類按照本身方式實現的接口:

//添加主成分
- (void)addMainMaterial;

//添加輔助成分
- (void)addIngredients;
複製代碼

由於熱飲的第一步都是一致的(準備熱水),因此第一步驟的接口沒有暴露出來給子類實現,而是直接在當前類實現了,這也就是模板方法的一個能夠複用代碼的優勢。

OK,咱們如今建立好了算法類,那麼根據上面的需求,咱們接着建立三個具體算法類:

  • HotDrinkTea : 熱茶
  • HotDrinkLatte : 熱拿鐵
  • HotDrinkAmericano: 熱美式
//================== HotDrinkTea.h ==================

@interface HotDrinkTea : HotDrink

@end



//================== HotDrinkTea.m ==================

@implementation HotDrinkTea


- (void)addMainMaterial{
    
    NSLog(@"add tea leaf");
}


- (void)addIngredients{
    
    NSLog(@"add nothing");
}


@end
複製代碼

熱茶在addMainMaterial步驟裏面是添加了茶葉,而在addIngredients步驟沒有作任何事情(這裏先假定是純的茶葉)。

相似地,咱們看一下兩種熱咖啡的實現。首先是熱拿鐵HotDrinkLatte:

//================== HotDrinkLatte.h ==================

@interface HotDrinkLatte : HotDrink

@end



//================== HotDrinkLatte.m ==================

@implementation HotDrinkLatte

- (void)addMainMaterial{
    
    NSLog(@"add ground coffee");
}


- (void)addIngredients{
    
    NSLog(@"add milk");
}


@end
複製代碼

熱拿鐵在addMainMaterial步驟裏面是添加了咖啡粉,而在addIngredients步驟添加了牛奶。

下面再看一下熱美式HotDrinkAmericano

//================== HotDrinkAmericano.h ==================

@interface HotDrinkAmericano : HotDrink

@end



//================== HotDrinkAmericano.m ==================

@implementation HotDrinkAmericano

- (void)addMainMaterial{
    
    NSLog(@"add ground coffee");
}


- (void)addIngredients{
    
    NSLog(@"add nothing");
}

@end
複製代碼

熱美式在addMainMaterial步驟裏面是添加了咖啡粉,而在addIngredients步驟沒有作任何事,由於美式就是純的咖啡,理論上除了水和咖啡不須要添加任何其餘東西。

到如今三種熱飲類建立好了,咱們如今分別製做這三種熱飲,並看一下日至輸出:

===== Begin to making HotDrinkTea =====
prepare hot water
add tea leaf
add nothing
===== Begin to making HotDrinkLatte =====
prepare hot water
add ground coffee
add milk
===== Begin to making HotDrinkAmericano =====
prepare hot water
add ground coffee
add nothing
複製代碼

上面的日至輸出準確無誤地反映了咱們所定義的這三種熱飲製做過程:

  • 熱茶:準備熱水 + 茶葉
  • 熱拿鐵:準備熱水 + 咖啡 + 牛奶
  • 熱美式:準備熱水 + 咖啡

下面看一下上面代碼對應的類圖。

代碼對應的類圖

模板方法模式代碼示例類圖

優勢

  • 複用性高:將相同的代碼放在父類中,而不一樣的部分則由子類實現
  • 擴展性高:能夠經過建立不一樣的子類來擴展不一樣的算法
  • 符合開閉原則:可變與不可變的部分分離,並且不一樣的可變部分(子類)也是相互分離的,因此符合了開閉原則

缺點

  • 致使類的個數增長:對於每個算法實現都須要一個子類,若是實現過多的話會致使類的個數增長
  • 由繼承關係致使的缺點:若是父類須要增長或減小它的行爲,則全部的子類都須要同步修改一次

iOS SDK 和 JDK中的應用

  • 在 iOS SDK 中,咱們能夠重寫 UIViewdrawRect:方法能夠自定義繪圖,是模板方法模式的一種實踐。
  • 在JDK中,java.lang.Runnable是使用JDK的經典場景:Runnable接口能夠做爲抽象的命令,而實現了Runnable的線程便是具體的命令。

二. 策略模式

定義

策略模式(Strategy Pattern):定義一系列算法,將每個算法封裝起來,並讓它們能夠相互替換。

適用場景

有時候在實現某一個功能的時可能會有多個方案:咱們須要讓系統能夠動態靈活地更換方案;並且也可以讓開發者方便地增長新的方案或刪除舊的方案。

若是咱們將全部的方案硬編碼在同一個類中,那麼在從此修改,添加,刪除某個方案的時候就會改動原有類,這是違反開閉原則的。

其實咱們能夠定義一些獨立的類來封裝不一樣的解決方案,每個類封裝一個具體的方案,這些不一樣的方案就是咱們所說的策略。並且咱們能夠用一個抽象的策略類來保證這些策略的一致性,這就是策略模式的設計方案。

如今咱們清楚了策略模式的適用場景,下面看一下策略模式的成員和類圖。

成員與類圖

成員

策略模式除了客戶端以外共有三個成員:

  • 環境類(Context):環境類內部持有一個具體策略類的實例,這個實例就是當前的策略,能夠供客戶端使用
  • 抽象策略類(Strategy):抽象策略類聲明具體策略類須要實現的接口,這個接口同時也是提供給客戶端調用的接口
  • 具體策略類(Concrete Strategy):具體策略類實現抽象策略類聲明的接口,每一個具體策略類都有本身獨有的實現方式,即表明不一樣策略

下面咱們經過類圖來看一下各個成員之間的關係。

模式類圖

策略模式類圖

代碼示例

場景概述

模擬一個兩個整數能夠隨意替換加減乘除算法的場景。

場景分析

在該場景中,傳入的兩個整數參數是不變的,可是對於這兩個整數的具體操做能夠靈活切換,那麼咱們可使用策略模式:將每一個操做(算法)封裝起來,在須要替換的時候將Context類持有的具體策略實例更新便可。

代碼實現

首先咱們定義好抽象策略類和具體策略類:

由於是針對兩個整數的操做,因此在抽象策略類中,咱們只需定義一個傳入兩個整數的接口便可。

抽象策略類TwoIntOperation:

//================== TwoIntOperation.h ==================

@interface TwoIntOperation : NSObject

- (int)operationOfInt1:(int)int1 int2:(int)int2;

@end



//================== TwoIntOperation.m ==================

@implementation TwoIntOperation

- (int)operationOfInt1:(int)int1 int2:(int)int2{
    
    //implenting by sub classes;
    return 0;
}

@end
複製代碼

接着咱們根據加減乘除四種運算,來分別定義四個具體策略類:

加法TwoIntOperationAdd

//================== TwoIntOperationAdd.h ==================

@interface TwoIntOperationAdd : TwoIntOperation

@end



//================== TwoIntOperationAdd.m ==================

@implementation TwoIntOperationAdd

- (int)operationOfInt1:(int)int1 int2:(int)int2{
    
    NSLog(@"==== adding ====");
    
    return int1 + int2;
}

@end
複製代碼

減法TwoIntOperationSubstract

//================== TwoIntOperationSubstract.h ==================

@interface TwoIntOperationSubstract : TwoIntOperation

@end



//================== TwoIntOperationSubstract.m ==================

@implementation TwoIntOperationSubstract

- (int)operationOfInt1:(int)int1 int2:(int)int2{
    
    NSLog(@"==== Substract ====");
    return int1 - int2;
}
@end
複製代碼

乘法TwoIntOperationMultiply:

//================== TwoIntOperationMultiply.h ==================

@interface TwoIntOperationMultiply : TwoIntOperation

@end



//================== TwoIntOperationMultiply.m ==================

@implementation TwoIntOperationMultiply

- (int)operationOfInt1:(int)int1 int2:(int)int2{
    
    NSLog(@"==== multiply ====");
    
    return int1 * int2;
}

@end
複製代碼

除法TwoIntOperationDivision:

//================== TwoIntOperationDivision.h ==================

@interface TwoIntOperationDivision : TwoIntOperation

@end



//================== TwoIntOperationDivision.m ==================

@implementation TwoIntOperationDivision

- (int)operationOfInt1:(int)int1 int2:(int)int2{
    
    NSLog(@"==== division ====");
    return int1/int2;
}

@end
複製代碼

如今關於算法的類都聲明好了,咱們最後聲明一下 Context 類:

//================== Context.h ==================

@interface Context : NSObject

- (instancetype)initWithOperation: (TwoIntOperation *)operation;

- (void)setOperation:(TwoIntOperation *)operation;

- (int)excuteOperationOfInt1:(int)int1 int2:(int)int2;

@end



//================== Context.m ==================

@implementation Context
{
    TwoIntOperation *_operation;
}

- (instancetype)initWithOperation: (TwoIntOperation *)operation{

    self = [super init];
    if (self) {
        //injection from instane initialization
        _operation = operation;
    }
    return self;
}

- (void)setOperation:(TwoIntOperation *)operation{
    
    //injection from setting method
    _operation = operation;
}

- (int)excuteOperationOfInt1:(int)int1 int2:(int)int2{
    
    //return value by constract strategy instane
    return [_operation operationOfInt1:int1 int2:int2];
}

@end
複製代碼

Context類在構造器(init方法)注入了一個具體策略實例並持有它,並且Context也提供了set方法,讓外部注入進來具體策略類的實例。

而策略的具體執行是經過Context的接口excuteOperationOfInt1:int2。這個接口是提供給客戶端調用的;並且在它的內部其實調用的是當前持有的策略實例的執行策略的方法。

因此若是想使用哪一種策略,只要將具體策略的實例傳入到Context實例便可。

如今全部的類都定義好了,下面咱們看一下具體如何使用:

int int1 = 6;
int int2 = 3;
    
NSLog(@"int1: %d int2: %d",int1,int2);
    
//Firstly, using add operation
TwoIntOperationAdd *addOperation = [[TwoIntOperationAdd alloc] init];
Context *ct = [[Context alloc] initWithOperation:addOperation];
int res1 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of adding : %d",res1);
    
//Changing to multiple operation
TwoIntOperationMultiply *multiplyOperation = [[TwoIntOperationMultiply alloc] init];
[ct setOperation:multiplyOperation];
int res2 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of multiplying : %d",res2);
    
    
//Changing to substraction operation
TwoIntOperationSubstract *subOperation = [[TwoIntOperationSubstract alloc] init];
[ct setOperation:subOperation];
int res3 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of substracting : %d",res3);
    
    
//Changing to division operation
TwoIntOperationDivision *divisionOperation = [[TwoIntOperationDivision alloc] init];
[ct setOperation:divisionOperation];
int res4 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of dividing : %d",res4);
複製代碼

看一下日至輸出:

[13431:1238320] int1: 6    int2: 3
[13431:1238320] ==== adding ====
[13431:1238320] result of adding : 9
[13431:1238320] ==== multiply ====
[13431:1238320] result of multiplying : 18
[13431:1238320] ==== Substract ====
[13431:1238320] result of substracting : 3
[13431:1238320] ==== division ====
[13431:1238320] result dividing : 2
複製代碼

在上面的例子中,首先咱們要使用加法,因此 實例化了加法策略類並傳入到了Context類的構造器中。

然後續的乘法,減法,除法的更換,則是分別將它們的策略實例傳入到了Context的set方法中,並執行便可。

下面看一下上面代碼對應的類圖。

代碼對應的類圖

策略模式代碼示例類圖

優勢

  • 策略模式遵循開閉原則,用戶能夠在不修改原有系統的前提下選擇和更換算法
  • 避免使用多重條件判斷
  • 能夠靈活地增長新的算法或行爲
  • 提升算法和策略的安全性:能夠封裝策略的具體實現,調用者只須要知道不一樣策略之間的區別就能夠

缺點

  • 客戶端必須知道當前全部的具體策略類,並且須要自行決定使用哪個策略類
  • 若是可選的方案過多,會致使策略類數量激增。

iOS SDK 和 JDK中的應用

  • JDK中的Comparator是策略模式的實現,可使用不一樣的子類,也就是具體策略來解決不一樣的需求。

三. 責任鏈模式

定義

責任鏈模式(Chain of Responsibility Pattern):爲請求建立了一個接收者對象的鏈,每一個接收者都包含對另外一個接收者的引用。若是一個對象不能處理該請求,那麼它會把相同的請求傳給下一個接收者,依此類推。

適用場景

在處理某個請求的時候,解決策略因條件不一樣而不一樣。這時,相對於使用if-else來區分不一樣的條件和對應的解決策略,咱們可使用責任鏈模式,將不一樣條件和對應的解決策略封裝到一個類中,即不一樣的處理者。而後將這些處理者組成責任鏈,在當前處理者沒法處理或不符合當前條件時,將請求傳遞給下一個處理者。

如今咱們清楚了責任鏈模式的適用場景,下面看一下責任鏈模式的成員和類圖。

成員與類圖

成員

責任鏈模式的結構比較簡單,不包括客戶端只有兩個成員:

  • 處理者(Handler):處理者定義處理請求的接口
  • 具體處理者(Concrete Handler): 具體處理者實現處理者聲明的接口,負責處理請求

模式類圖

責任鏈模式類圖

代碼示例

場景概述

模擬一個 ATM 取現金的場景:ATM機器有50,20,10面值的紙幣,根據用戶須要提取的現金金額來輸出紙幣張數最少的等價金額的紙幣。

好比用戶須要取130元,則ATM須要輸出2張50面額的紙幣,1張20面額的紙幣,1張10面額的紙幣;而不是6張20面額的紙幣加1張10面額的紙幣。

場景分析

顯然,爲了輸出最少張數的紙幣,ATM在計算的時候是從面額最大的紙幣開始計算的。

若是不使用責任鏈模式,咱們可能會寫一個do-while循環,在循環裏面再根據紙幣的面額在作if-else判斷,不斷去嘗試直到將面額除盡(沒有餘數)。可是若是將來面額的數值發生變化,或者添加新的面額的紙幣的話,咱們還須要更改判斷條件或增長if-else語句,這顯然違反了開閉原則。

可是若是使用責任鏈模式,咱們將每一個面值的紙幣當作責任鏈中的一個處理者(節點,node),自成一類,單獨作處理。而後將這些處理者按照順序鏈接起來(50,20,10),按照順序對用戶輸入的數值進行處理便可。

這樣作的好處是,若是之後修改面值或添加一種新的面值,咱們只須要修改其中某一個處理者或者新建一個處理者類,再從新插入到責任鏈的合適的位置便可。

下面咱們看一下如何用代碼來模擬該場景。

代碼實現

首先建立抽象處理者DispenseChainNode:

//================== DispenseChainNode.h ==================

@interface DispenseChainNode : NSObject <DispenseProtocol>
{
    @protected DispenseChainNode *_nextChainUnit;
}

- (void)setNextChainUnit:(DispenseChainNode *)chainUnit;

@end



//================== DispenseChainNode.m ==================

@implementation DispenseChainNode

- (void)setNextChainNode:(DispenseChainNode *)chainNode{
    
    _nextChainNode = chainNode;
}

- (void)dispense:(int)amount{
    
    return;
}

@end
複製代碼
  • DispenseChainNode是責任鏈節點,也就是具體處理者的父類,它持有DispenseChainNode的實例,用來保存當前節點的下一個節點。這個下一個節點的實例是經過setNextChainNode:方法注入進來的 並且,DispenseChainNode遵循<DispenseProtocol>協議,這個協議只有一個方法,就是dispense:方法,每一個節點都實現這個方法來對輸入的金額作處理。(dispense 單詞的意思是分配,分發)

如今咱們根據需求,建立具體處理者,也就是針對50,20,10面額的具體處理者:

50面額的具體處理者:

//================== DispenseChainNodeFor50Yuan.h ==================

@interface DispenseChainNodeFor50Yuan : DispenseChainNode

@end



//================== DispenseChainNodeFor50Yuan.m ==================

@implementation DispenseChainNodeFor50Yuan

- (void)dispense:(int)amount{
    
    int unit = 50;
    
    if (amount >= unit) {
        
        int count = amount/unit;
        int remainder = amount % unit;
        
        NSLog(@"Dispensing %d of %d",count,unit);
        
        if (remainder != 0) {
            [_nextChainNode dispense:remainder];
        }
        
    }else{
        
        [_nextChainNode dispense:amount];
    }
}


@end
複製代碼

20面額的具體處理者:

//================== DispenseChainNodeFor20Yuan.h ==================

@interface DispenseChainNodeFor20Yuan : DispenseChainNode

@end



//================== DispenseChainNodeFor20Yuan.m ==================

@implementation DispenseChainNodeFor20Yuan

- (void)dispense:(int)amount{
    
    int unit = 20;
    
    if (amount >= unit) {
        
        int count = amount/unit;
        int remainder = amount % unit;
        
        NSLog(@"Dispensing %d of %d",count,unit);
        
        if (remainder != 0) {
            [_nextChainNode dispense:remainder];
        }
        
    }else{
        
        [_nextChainNode dispense:amount];
    }
}


@end
複製代碼

10面額的具體處理者:

//================== DispenseChainNodeFor10Yuan.h ==================

@interface DispenseChainNodeFor10Yuan : DispenseChainNode

@end



//================== DispenseChainNodeFor10Yuan.m ==================

@implementation DispenseChainNodeFor10Yuan

- (void)dispense:(int)amount{
    
    int unit = 10;
    
    if (amount >= unit) {
        
        int count = amount/unit;
        int remainder = amount % unit;
        
        NSLog(@"Dispensing %d of %d",count,unit);
        
        if (remainder != 0) {
            [_nextChainNode dispense:remainder];
        }
        
    }else{
        
        [_nextChainNode dispense:amount];
    }
}

@end
複製代碼

上面三個具體處理者在dispense:方法的處理都是相似的:

首先查看當前值是否大於面額

  • 若是大於面額
    • 將當前值除以當前面額
      • 若是沒有餘數,則中止,不做處理
      • 若是有餘數,則繼續將當前值傳遞給下一個具體處理者(責任鏈的下一個節點)
  • 若是小於面額:將當前值傳遞給下一個具體處理者(責任鏈的下一個節點)

如今咱們建立好了三個具體處理者,咱們再建立一個ATM類來把這些節點串起來:

//================== ATMDispenseChain.h ==================

@interface ATMDispenseChain : NSObject<DispenseProtocol>

@end



//================== ATMDispenseChain.m ==================

@implementation ATMDispenseChain
{
    DispenseChainNode *_chainNode;
}

- (instancetype)init{
    
    self = [super init];
    if(self){
        
        DispenseChainNodeFor50Yuan *chainNode50 = [[DispenseChainNodeFor50Yuan alloc] init];
        DispenseChainNodeFor20Yuan *chainNode20 = [[DispenseChainNodeFor20Yuan alloc] init]; 
        DispenseChainNodeFor10Yuan *chainNode10 = [[DispenseChainNodeFor10Yuan alloc] init];
        
         _chainNode = chainNode50;
        [_chainNode setNextChainNode:chainNode20];
        [chainNode20 setNextChainNode:chainNode10];
        
    }
    
    return self;
    
}



- (void)dispense:(int)amount{
    
    NSLog(@"==================================");
    NSLog(@"ATM start dispensing of amount:%d",amount);
    
    if (amount %10 != 0) {
        NSLog(@"Amount should be in multiple of 10");
        return;
    }

    [_chainNode dispense:amount];
    
}

@end
複製代碼

ATMDispenseChain這個類在初始化的時候就將三個具體處理者並按照50,20,10的順序鏈接起來,並持有一個DispenseChainNode的指針指向當前的具體處理者(也就是責任鏈的第一個節點,面額50的具體處理者,由於面額的處理是從50開始的)。

OK,如今咱們把三個具體處理者都封裝好了,能夠看一下如何使用:

ATMDispenseChain *atm = [[ATMDispenseChain alloc] init];
    
[atm dispense:230];
    
[atm dispense:70];
    
[atm dispense:40];
    
[atm dispense:10];

[atm dispense:8];
複製代碼

建立ATMDispenseChain的實例後,分別傳入一些數值來看一下處理的結果:

==================================
ATM start dispensing of amount:230
Dispensing 4 of 50
Dispensing 1 of 20
Dispensing 1 of 10
==================================
ATM start dispensing of amount:70
Dispensing 1 of 50
Dispensing 1 of 20
==================================
ATM start dispensing of amount:40
Dispensing 2 of 20
==================================
ATM start dispensing of amount:10
Dispensing 1 of 10
==================================
ATM start dispensing of amount:8
Amount should be in multiple of 10
複製代碼

從日誌的輸出能夠看出,咱們的責任鏈處理是沒有問題的,針對每一個不一樣的數值,ATMDispenseChain實例都做出了最正確的結果。

須要注意的是,該代碼示例中的責任鏈類(ATMDispenseChain)並無在上述責任鏈模式的成員中。不過此處沒必要作過多糾結,咱們在這裏只是在業務上稍微多作一點處理罷了。其實也徹底能夠不封裝這些節點,直接逐個調用setNextChainNode:方法組裝責任鏈,而後將任務交給第一個處理者便可。

需求完成了,是否能夠作個重構?

咱們回去看一下這三個具體處理者在dispense:方法的處理是很是類似的,他們的區別只有處理的面額數值的不一樣:而咱們實際上是建立了針對這三個面值的類,並將面值(50,20,10)硬編碼在了這三個類中。這樣作是有缺點的,由於若是後面的面額大小變了,或者增長或者減小面額的話咱們會修改這些類或添加刪除這些類(即便這也比不使用責任鏈模式的if-else要好一些)。

所以咱們能夠不建立這些與面額值硬編碼的具體處理類,而是在初始化的時候直接將面額值注入到構造方法裏面便可!這樣一來,咱們能夠隨意調整和修改面額了。下面咱們作一下這個重構:

首先刪除掉三個具體處理者DispenseChainNodeFor50Yuan,DispenseChainNodeFor20Yuan,DispenseChainNodeFor10Yuan

接着在DispenseChainNode添加傳入面額值的初始化方法以及面額值的成員變量:

//================== ADispenseChainNode.h ==================

@interface DispenseChainNode : NSObject <DispenseProtocol>
{
    @protected DispenseChainNode *_nextChainNode;
    @protected int _dispenseValue;
}

- (instancetype)initWithDispenseValue:(int)dispenseValue;

- (void)setNextChainNode:(DispenseChainNode *)chainNode;


@end



//================== ADispenseChainNode.m ==================

@implementation DispenseChainNode

- (instancetype)initWithDispenseValue:(int)dispenseValue
{
    self = [super init];
    if (self) {
        _dispenseValue = dispenseValue;
    }
    return self;
}

- (void)setNextChainNode:(DispenseChainNode *)chainNode{
    
    _nextChainNode = chainNode;
}

- (void)dispense:(int)amount{
    
    if (amount >= _dispenseValue) {
        
        int count = amount/_dispenseValue;
        int remainder = amount % _dispenseValue;
        
        NSLog(@"Dispensing %d of %d",count,_dispenseValue);
        
        if (remainder != 0) {
            [_nextChainNode dispense:remainder];
        }
        
    }else{
        
        [_nextChainNode dispense:amount];
    }
}

@end
複製代碼

咱們給DispenseChainNode添加了initWithDispenseValue:方法後,就能夠根據需求隨意生成不一樣面額的具體處理者了。

接着咱們思考一下以前的ATMDispenseChain能夠作哪些改變?

既然DispenseChainNode能夠根據不一樣的面額值生成處理不一樣面額的具體處理者實例,那麼對於串聯多個具體處理者的類ATMDispenseChain是否是也能夠添加一個注入面額數組的初始化方法呢?好比輸入[50,20,10]的數組就能夠生成50,20,10面額的具體處理者了;並且數組是有序的,傳入數組的元素順序就能夠是責任鏈中節點的順序。

思路有了,咱們看一下具體實現:

//================== ATMDispenseChain.m ==================

@implementation ATMDispenseChain
{
    DispenseChainNode *_firstChainNode;
    DispenseChainNode *_finalChainNode;
    int _minimumValue;
}


- (instancetype)initWithDispenseNodeValues:(NSArray *)nodeValues{
    
    self = [super init];
    
    if(self){
        
        NSUInteger length = [nodeValues count];
        
        [nodeValues enumerateObjectsUsingBlock:^(NSNumber * nodeValue, NSUInteger idx, BOOL * _Nonnull stop) {
            
            DispenseChainNode *iterNode = [[DispenseChainNode alloc] initWithDispenseValue:[nodeValue intValue]];
            
            if (idx == length - 1 ) {
                _minimumValue = [nodeValue intValue];
            }
            
            if (!self->_firstChainNode) {
                
                 //because this chain is empty, so the first node and the final node will refer the same node instance
                 self->_firstChainNode =  iterNode;
                 self->_finalChainNode =  self->_firstChainNode;
                
            }else{
                
                //appending the next node, and setting the new final node
                [self->_finalChainNode setNextChainNode:iterNode];
                 self->_finalChainNode = iterNode;
            }
        }];
    }
    
    return self;
}


- (void)dispense:(int)amount{
    
    NSLog(@"==================================");
    NSLog(@"ATM start dispensing of amount:%d",amount);
    
    if (amount % _minimumValue != 0) {
        NSLog(@"Amount should be in multiple of %d",_minimumValue);
        return;
    }

    [ _firstChainNode dispense:amount];
    
}

@end
複製代碼

重構後的ATMDispenseChain類新增了initWithDispenseNodeValues:方法,須要從外部傳入面額值的數組。在這個方法裏面根據傳入的數組構造了整條責任鏈。

而在dispense:方法裏面則是從責任鏈的第一個節點來處理面額,並在方法最前面取最小面額的值來作邊界處理。

OK,到如今處理者類和責任鏈類都建立好了,咱們看一下如何使用:

NSArray *dispenseNodeValues = @[@(100),@(50),@(20),@(10)];

ATMDispenseChain *atm = [[ATMDispenseChain alloc] initWithDispenseNodeValues:dispenseNodeValues];
    
[atm dispense:230];
    
[atm dispense:70];
    
[atm dispense:40];
    
[atm dispense:10];
    
[atm dispense:8];
複製代碼

是否是感受簡潔多了?咱們只須要傳入一個面額值的數組便可構造出整條責任鏈並直接使用。來看一下日至輸出:

==================================
ATM start dispensing of amount:230
Dispensing 2 of 100
Dispensing 1 of 20
Dispensing 1 of 10
==================================
ATM start dispensing of amount:70
Dispensing 1 of 50
Dispensing 1 of 20
==================================
ATM start dispensing of amount:40
Dispensing 2 of 20
==================================
ATM start dispensing of amount:10
Dispensing 1 of 10
==================================
ATM start dispensing of amount:8
Amount should be in multiple of 10
複製代碼

從日誌的輸出結果上看,咱們重構後的責任鏈方案沒有問題。

下面看一下上面代碼對應的類圖。

代碼對應的類圖

重構前:

責任鏈模式代碼示例類圖一

重構後:

責任鏈模式代碼示例類圖二

優勢

  • 處理者之間的責任分離,處理者只要處理好本身的邏輯便可
  • 方便修改每一個處理者的處理邏輯,也方便刪除或者添加處理者,或者改變責任鏈中處理者的順序。

缺點

  • 由於須要在責任鏈上傳遞責任,直到找到合適的對象來處理,因此可能會致使處理的延遲。所以在延遲不容許太高的場景下不適合使用責任鏈模式。

iOS SDK 和 JDK中的應用

  • iOS SDK中的響應者鏈就是責任鏈模式的實踐:若是當前視圖沒法響應則傳遞給下一層級視圖。
  • servlet中的Filter能夠組成FilterChain,是責任鏈模式的一種實踐。

四. 狀態模式

定義

在狀態模式(State Pattern):容許一個對象在其內部狀態改變時,改變它的行爲。

適用場景

一個對象存在多個狀態,不一樣狀態下的行爲會有不一樣,並且狀態之間能夠相互轉換。

若是咱們經過if else來判斷對象的狀態,那麼代碼中會包含大量與對象狀態有關的條件語句,並且在添加,刪除和更改這些狀態的時候回比較麻煩;而若是使用狀態模式。將狀態對象分散到不一樣的類中,則能夠消除 if...else等條件選擇語句。

如今咱們清楚了狀態模式的適用場景,下面看一下狀態模式的成員和類圖。

成員與類圖

成員

狀態模式一共只有四個成員:

  • 環境類(Context):環境類引用了具體狀態的實例。環境類持有的具體狀態就是當前的狀態,能夠經過 set 方法將狀態實例注入到環境類中。
  • 抽象狀態類(State):抽象狀態類聲明具體狀態類須要實現的接口。
  • 具體狀態類(Concrete State):具體狀態類實現抽象狀態類聲明的接口。

下面經過類圖來看一下各個成員之間的關係:

模式類圖

狀態模式類圖

代碼示例

場景概述

模擬一個程序員一天的生活,他有四個狀態:

  1. 醒着
  2. 睡覺中
  3. 寫代碼中
  4. 吃飯中

看這幾個狀態應該是個很是愛寫代碼的程序員 ^ ^

場景分析

這個程序員有四個狀態,可是有些狀態之間是沒法切換的:好比從睡覺是沒法切換到寫代碼的(由於須要切換到醒着,而後才能到寫代碼);從吃飯中是沒法切換到醒着的,由於已經醒着了。

若是咱們不使用狀態模式,在切換狀態的時候可能會寫很多if-else判斷,並且隨着狀態的增多,這些分支會變得更多,難以維護。

而若是咱們使用狀態模式,則能夠將每一個狀態封裝到一個類中,便於管理;並且在增長或減小狀態時也會很方便。

下面咱們看一下如何用代碼來模擬該場景。

代碼實現

首先咱們定義狀態類:

//================== State.h ==================

@interface State : NSObject<ActionProtocol>
{
    @protected Coder *_coder;
}

- (instancetype)initWithCoder:(Coder *)coder;

@end



//================== State.m ==================

@implementation State

- (instancetype)initWithCoder:(Coder *)coder{
    
    self = [super init];
    if (self) {
        _coder = coder;
    }
    return self;
}

@end
複製代碼

狀態類持有一個coder,也就是程序員的實例,並遵循了ActionProtocol

//================== ActionProtocol.h ==================

@protocol ActionProtocol <NSObject>

@optional;

- (void)wakeUp;

- (void)fallAsleep;

- (void)startCoding;

- (void)startEating;

@end
複製代碼

ActionProtocol定義了程序員的一些動做,這些動做是程序員的平常活動,也是觸發狀態切換的動做,所以State也須要遵循這個協議,由於它的子類須要實現這些操做。

接下來咱們看一下State的子類,根據上面說的四種狀態,咱們定義下面四個狀態子類:

StateAwake:

//================== StateAwake.h ==================

@interface StateAwake : State

@end

@implementation StateAwake

- (void)wakeUp{
    
    NSLog(@"Already awake, can not change state to awake again");
}

- (void)startCoding{
    
    NSLog(@"Change state from awake to coding");
    [_coder setState:(State *)[_coder stateCoding]];
}

- (void)startEating{
    
    NSLog(@"Change state from awake to eating");
    [_coder setState:(State *)[_coder stateEating]];
}


- (void)fallAsleep{
    
    NSLog(@"Change state from awake to sleeping");
    [_coder setState:(State *)[_coder stateSleeping]];
}

@end
複製代碼

StateSleeping:

//================== StateSleeping.h ==================

@interface StateSleeping : State

@end



//================== StateSleeping.m ==================

@implementation StateSleeping

- (void)wakeUp{
    
    NSLog(@"Change state from sleeping to awake");
    [_coder setState:(State *)[_coder stateAwake]];
}


- (void)startCoding{
    
    NSLog(@"Already sleeping, can not change state to coding");
}

- (void)startEating{
    
    NSLog(@"Already sleeping, can change state to eating");
}


- (void)fallAsleep{
    
    NSLog(@"Already sleeping, can not change state to sleeping again");
}

@end
複製代碼

StateEating:

//================== StateEating.h ==================

@interface StateEating : State

@end



//================== StateEating.m ==================

@implementation StateEating

- (void)wakeUp{
    
    NSLog(@"Already awake, can not change state to awake again");
}


- (void)startCoding{
    
    NSLog(@"New idea came out! change state from eating to coding");
    [_coder setState:(State *)[_coder stateCoding]];
}

- (void)startEating{
    
    NSLog(@"Already eating, can not change state to eating again");
}


- (void)fallAsleep{
    
    NSLog(@"Too tired, change state from eating to sleeping");
    [_coder setState:(State *)[_coder stateSleeping]];
}



@end
複製代碼

"StateCoding":

//================== StateCoding.h ==================

@interface StateCoding : State

@end



//================== StateCoding.m ==================

@implementation StateCoding

- (void)wakeUp{
    
    NSLog(@"Already awake, can not change state to awake again");
}


- (void)startCoding{
    
    NSLog(@"Already coding, can not change state to coding again");
}

- (void)startEating{
    
    NSLog(@"Too hungry, change state from coding to eating");
    [_coder setState:(State *)[_coder stateEating]];
}


- (void)fallAsleep{
    
    NSLog(@"Too tired, change state from coding to sleeping");
    [_coder setState:(State *)[_coder stateSleeping]];
}

@end
複製代碼

從上面的類能夠看出,在有些狀態之間的轉換是失效的,有些是能夠的。 好比相同狀態的切換是無效的;從 sleeping沒法切換到coding,可是反過來能夠,由於可能寫代碼累了就直接睡了。

下面咱們看一下程序員類的實現:

//================== Coder.h ==================

@interface Coder : NSObject<ActionProtocol>

@property (nonatomic, strong) StateAwake *stateAwake;
@property (nonatomic, strong) StateCoding *stateCoding;
@property (nonatomic, strong) StateEating *stateEating;
@property (nonatomic, strong) StateSleeping *stateSleeping;

- (void)setState:(State *)state;

@end



//================== Coder.m ==================

@implementation Coder
{
    State *_currentState;
}

- (instancetype)init{
    
    self = [super init];
    if (self) {
        
        _stateAwake = [[StateAwake alloc] initWithCoder:self];
        _stateCoding = [[StateCoding alloc] initWithCoder:self];
        _stateEating = [[StateEating alloc] initWithCoder:self];
        _stateSleeping = [[StateSleeping alloc] initWithCoder:self];
        
        _currentState = _stateAwake;
    }
    return self;
}

- (void)setState:(State *)state{
    
    _currentState = state;
}

- (void)wakeUp{
    
    [_currentState wakeUp];
}

- (void)startCoding{
    
    [_currentState startCoding];
}

- (void)startEating{
    
    [_currentState startEating];
}


- (void)fallAsleep{
    
    [_currentState fallAsleep];
}

@end
複製代碼

從上面的代碼咱們能夠看到,程序員類持有一個當前的狀態的實例,在初始化後默認的狀態爲awake,並對外提供一個setState:的方法來切換狀態。並且在初始化方法裏,咱們實例化了全部的狀態,目的是在切換狀態中時使用,詳見具體狀態類的方法:

- (void)startEating{
    
    NSLog(@"Too hungry, change state from coding to eating");
    [_coder setState:(State *)[_coder stateEating]];
}
複製代碼

上面這段代碼有點繞,可能須要多看幾遍源碼才能理解(這裏面[_coder stateEating]是調用了coder的一個get方法,返回了stateEating這個實例)。

最後,在程序員的動做方法裏面,實際上調用的是當前狀態對應的方法(這也就是爲什麼程序員類和狀態類都要遵循ActionProtocol的緣由)。

這樣,咱們的狀態類,狀態子類,程序員類都聲明好了。咱們看一下如何使用:

Coder *coder = [[Coder alloc] init];
    
//change to awake.. failed
[coder wakeUp];//Already awake, can not change state to awake again
    
//change to coding
[coder startCoding];//Change state from awake to coding
    
//change to sleep
[coder fallAsleep];//Too tired, change state from coding to sleeping
    
//change to eat...failed
[coder startEating];//Already sleeping, can change state to eating
    
//change to wake up
[coder wakeUp];//Change state from sleeping to awake

//change wake up...failed
[coder wakeUp];//Already awake, can not change state to awake again
    
//change to eating
[coder startEating];//Change state from awake to eating
    
//change to coding
[coder startCoding];//New idea came out! change state from eating to coding
    
//change to sleep
[coder fallAsleep];//Too tired, change state from coding to sleeping
複製代碼

在上面的代碼裏,咱們實例化了一個程序員類,接着不斷調用一些觸發狀態改變的方法。咱們把每次狀態切換的日至輸出註釋到了代碼右側,能夠看到在一些狀態的切換是不容許的:

  • 好比從上到下的第一個[coder wakeUp]:由於程序員對象初始化後默認是awake狀態,因此沒法切換到相同的狀態
  • 好比從上到下的第一個[coder startEating]:在睡覺時是沒法直接切換到eating狀態;而在後面wake之後,再執行[coder startEating]就成功了。

從上面的例子能夠看出,使用狀態模式不須要去寫if-else,並且若是從此想添加一個狀態,只須要再建立一個狀態子類,並在新的狀態子類添加好對全部狀態的處理,並在以前的狀態子類中添加上對新狀態的處理便可。即使咱們修改了以前定義好的狀態子類,可是這樣也總比使用龐大的if-else要方便多。

下面看一下上面代碼對應的類圖。

代碼對應的類圖

狀態模式代碼示例類圖

優勢

  1. 把各類狀態的轉換邏輯,分佈到不一樣的類中,減小相互間的依賴。

缺點

  1. 增長新的狀態類須要修改狀態轉換的源碼,並且增長新的行爲也要修改原來的狀態類(前提是新的行爲和原來的狀態有關係)。
  2. 過多的狀態會增長系統中的類的個數,增長系統的複雜性。

iOS SDK 和 JDK中的應用

  • javax包下的LifyCycle是狀態模式的一種實現

五. 命令模式

定義

命令模式(Command Pattern):命令(或請求)被封裝成對象。客戶端將命令(或請求)對象先傳遞給調用對象。調用對象再把該命令(或請求)對象傳給合適的,可處理該命令(或請求)的對象來作處理。

由定義能夠看出,在命令模式中,命令被封裝成了對象,而發送命令的客戶端與處理命令的接收者中間被調用對象隔開了,這種設計的緣由或者適用的場景是什麼樣的呢?

適用場景

在有些場景下,任務的處理可能不是須要當即執行的:可能須要記錄(日至),撤銷或重試(網絡請求)。那麼在這些場景下,若是任務的請求者和執行者是緊耦合狀態下的話就可能會將不少其餘執行策略的代碼和當即執行的代碼混合到一塊兒。

這些其餘執行策略,咱們暫時稱之爲控制和管理策略,而若是咱們若是想控制和管理請求,就須要:

  1. 把請求抽象出來
  2. 讓另一個角色來負責控制和管理請求的任務

所以命令模式就是爲此場景量身打造的,它經過:

  1. 把請求封裝成對象
  2. 使用調用者在客戶端和請求處理者之間來作一個「攔截」,方便對請求對象作控制和管理。

如今咱們清楚了命令模式的適用場景,下面看一下命令模式的成員和類圖。

成員與類圖

成員

不包括請求的發起者(客戶端),命令模式共有四個成員:

  • 抽象命令類(Command):命令類負責聲明命令的接口。
  • 具體命令類(Concrete Command):具體命令類負責實現抽象命令類聲明的接口
  • 調用者(Invoker):調用者負責將具體命令類的實例傳遞給接收者
  • 接收者(Receiver):接收者負責處理命令

下面經過類圖來看一下命令模式各個成員之間的關係:

模式類圖

命令模式類圖

代碼示例

場景概述

模擬一個使用遙控器開燈和關燈的例子。

場景分析

在這個例子中,使用遙控器的人就是客戶端,TA發起開啓或關閉燈的命令給遙控器(調用者)。而後調用者將命令傳遞給接收者(燈)。

在這裏,人是不直接接觸燈的,開啓和關閉的命令是經過遙控器來作的轉發,最後傳達給燈來執行。

下面咱們看一下如何用代碼來模擬該場景。

代碼實現

首先咱們建立接收者,燈類:

//================== Light.h ==================

@interface Light : NSObject

- (void)lightOn;

- (void)lightOff;

@end



//================== Light.m ==================

@implementation Light


- (void)lightOn{
    
    NSLog(@"Light on");
}


- (void)lightOff{
    
    NSLog(@"Light off");
}

@end
複製代碼

燈類聲明並實現了兩個接口:開燈接口和關燈接口,來讓外部執行開燈和關燈的操做。

接着咱們建立抽象命令類和具體命令類:

抽象命令類:

//================== Command.h ==================

@interface Command : NSObject

- (void)excute;

@end



//================== Command.m ==================

@implementation Command

@end
複製代碼

抽象命令類聲明瞭一個執行命令的接口excute,這個接口由它的子類,也就是具體命令類來實現。

由於這裏面只有開燈和關燈兩種命令,因此咱們建立兩個具體命令類來繼承上面的抽象命令類:

開燈命令CommandLightOn

//================== CommandLightOn.h ==================

@interface CommandLightOn : Command

- (instancetype)initWithLight:(Light *)light;

@end


//================== CommandLightOn.m ==================

@implementation CommandLightOn
{
    Light *_light;
}

- (instancetype)initWithLight:(Light *)light{
    
    self = [super init];
    if (self) {
        _light = light;
    }
    return self;
}

- (void)excute{
    
    [_light lightOn];
}
複製代碼

關燈命令CommandLightOff

//================== CommandLightOff.h ==================

@interface CommandLightOff : Command

- (instancetype)initWithLight:(Light *)light;

@end



//================== CommandLightOff.m ==================
@implementation CommandLightOff
{
    Light *_light;
}

- (instancetype)initWithLight:(Light *)light{
    
    self = [super init];
    if (self) {
        _light = light;
    }
    return self;
}

- (void)excute{
    
    [_light lightOff];
}
複製代碼

咱們能夠看到這兩個具體命令類分別以本身的方式實現了它們的父類聲明的excute接口。

最後咱們建立連接客戶端和接收者的調用者類,也就是遙控器類RemoteControl

//================== RemoteControl.h ==================

@interface RemoteControl : NSObject

- (void)setCommand:(Command *)command;

- (void)pressButton;

@end



//================== RemoteControl.m ==================

@implementation RemoteControl
{
    Command *_command;
}


- (void)setCommand:(Command *)command{
    
    _command = command;
}

- (void)pressButton{
    
    [_command excute];
}

@end
複製代碼

遙控器類使用set方法注入了具體命令類,並向外提供了pressButton這個方法來內部調用已傳入的具體命令類的excute方法。

最後咱們看一下客戶端是如何操做這些類的:

//================== client ==================

//init Light and Command instance
//inject light instance into command instance
Light *light = [[Light alloc] init];
CommandLightOn *co = [[CommandLightOn alloc] initWithLight:light];
    
//set command on instance into remote control instance
RemoteControl *rm = [[RemoteControl alloc] init];
[rm setCommand:co];
    
//excute command(light on command)
[rm pressButton];
    

//inject light instance into command off instance
CommandLightOff *cf = [[CommandLightOff alloc] initWithLight:light];

//change to off command
[rm setCommand:cf];

//excute command(light close command)
[rm pressButton];
複製代碼

看一下日至輸出:

[11851:1190777] Light on
[11851:1190777] Light off
複製代碼

從上面的代碼能夠看到,咱們首先準備好具體命令類的實例,而後將其傳遞給遙控器類,最後觸發遙控器的pressButton方法來間接觸發light對象的相應操做。

下面看一下上面代碼對應的類圖。

代碼對應的類圖

命令模式代碼示例類圖

優勢

  • 將命令的發起者和命令的執行者分離,下降系統的耦合度
  • 便於批量處理命令,好比日至隊列的實現;便於命令的撤銷或重試,好比網絡請求等

缺點

  • 須要針對每個命令建立一個命令對象。若是系統中的命令過多,會形成系統中存在大量的命令類,提升系統的複雜度。

iOS SDK 和 JDK中的應用

  • 在JDK中,java.lang.Runnable是使用命令模式的經典場景,Runnable接口能夠做爲抽象的命令,而實現了Runnable的線程便是具體的命令。

六. 觀察者模式

定義

觀察者模式(Observer Pattern):定義對象間的一種一對多的依賴關係,使得每當一個對象狀態發生改變時,其相關依賴對象均可以到通知並作相應針對性的處理。

適用場景

凡是涉及到一對一或者一對多的對象交互場景均可以使用觀察者模式。一般咱們使用觀察者模式實現一個對象的改變會令其餘一個或多個對象發生改變的需求,好比換膚功能,監聽列表滾動的偏移量等等。

如今咱們清楚了觀察者模式的適用場景,下面看一下觀察者模式的成員和類圖。

成員與類圖

成員

觀察者模式有四個成員:

  • 目標(Subject):目標是被觀察的角色,聲明添加和刪除觀察者以及通知觀察者的接口。
  • 具體目標(Concrete Subject):具體目標實現目標類聲明的接口,保存全部觀察者的實例(經過集合的形式)。在被觀察的狀態發生變化時,給全部登記過的觀察者發送通知。
  • 觀察者(Observer):觀察者定義具體觀察者的更新接口,以便在收到通知時實現一些操做。
  • 具體觀察者(Concrete Observer):具體觀察者實現抽象觀察者定義的更新接口。

下面經過類圖來看一下各個成員之間的關係:

模式類圖

觀察者模式類圖

代碼示例

場景概述

模擬這樣的一個場景:客戶(投資者)訂閱理財顧問的建議購買不一樣價格的股票。當價格信息變化時,全部客戶會收到通知(可使短信,郵件等等),隨後客戶查看最新數據並進行操做。

場景分析

一個理財顧問可能服務於多個客戶,並且消息須要及時傳達到各個客戶那邊;而客戶接收到這些消息後,須要對這些消息作出相應的措施。這種一對多的通知場景咱們可使用觀察者模式:理財顧問是被觀察的目標(Subject),而TA的客戶則是觀察者(Observer)。

下面咱們看一下如何用代碼來模擬該場景。

代碼實現

首先咱們定義觀察者Observer:

//================== Observer.h ==================

@interface Observer : NSObject
{
    @protected Subject *_subject;
}

- (instancetype)initWithSubject:(Subject *)subject;

- (void)update;

@end



//================== Observer.m ==================

@implementation Observer

- (instancetype)initWithSubject:(Subject *)subject{
    
    self = [super init];
    if (self) {
        _subject = subject;
       [_subject addObserver:self];
    }
    return self;
}

- (void)update{
    
    NSLog(@"implementation by subclasses");
}
複製代碼

Observer類是具體觀察者的父類,它聲明瞭一個傳入目標類(Subject)的構造方法並在構造方法裏持有這個傳入的實例。並且在這個構造方法裏,調用了Subject的‘添加觀察者’的方法,即addObserver:,目的是將當前的觀察者實例放入Subject的用來保存觀察者實例的集合中(具體操做能夠在下面講解Subject類的部分看到)

另外它也定義了update方法供子類使用。

下面咱們看一下具體觀察者類Investor:

//================== Investor.h ==================

@interface Investor : Observer

@end



//================== Investor.m ==================

@implementation Investor

- (void)update{

    float buyingPrice = [_subject getBuyingPrice];
    NSLog(@"investor %p buy stock of price:%.2lf",self,buyingPrice);    
}

@end
複製代碼

具體觀察者實現了該協議中定義的方法update方法,在這個方法裏面,首先經過getBuyingPrice方法得到到最新的在監聽的數據buyingPrice,而後再作其餘操做。這裏爲了方便展現,直接使用日至打印出當前的具體觀察者實例的內存地址和當前監聽的最新值。

下面咱們聲明一下目標類和具體目標類:

目標類Subject

//================== Subject.h ==================

@interface Subject : NSObject
{
    @protected float _buyingPrice;
    @protected NSMutableArray <Observer *>*_observers;
}

- (void)addObserver:(Observer *) observer;


- (void)removeObserver:(Observer *) observer;


- (void)notifyObservers;


- (void)setBuyingPrice:(float)price;


- (double)getBuyingPrice;


@end




//================== Subject.m ==================

@implementation Subject

- (instancetype)init{
    
    self = [super init];
    if (self) {
        _observers = [NSMutableArray array];
    }
    return self;
}


- (void)addObserver:( Observer * ) observer{
    
    [_observers addObject:observer];
}


- (void)removeObserver:( Observer *) observer{
    
    [_observers removeObject:observer];
}


- (void)notifyObservers{
    
    [_observers enumerateObjectsUsingBlock:^(Observer *  _Nonnull observer, NSUInteger idx, BOOL * _Nonnull stop) {
        
        [observer update];
    }];
}


- (void)setBuyingPrice:(float)price{
    
    _buyingPrice = price;
    
    [self notifyObservers];
}


- (double)getBuyingPrice{
    
    return _buyingPrice;
}


@end
複製代碼

目標類持有一個可變數組,用來保存觀察本身的觀察者們;而且還提供了增長,刪除觀察者的接口,也提供了通知全部觀察者的接口。

並且它持有一個數據buyingPrice,這個數據就是讓外部觀察者觀察的數據。尤爲注意它向外界提供的setBuyingPrice:方法:當外部調用這個方法,也就是要更新buyingPrice這個數據時,目標類調用了notifyObservers方法來告知當前全部觀察本身的觀察者們:我更新了。

getBuyingPrice就是用來返回當前的buyingPrice的值的,通常是在觀察者們收到更新通知後,主動調動這個方法獲取的(具體看上面Investor類的實現)。

OK,如今抽象目標類定義好了,下面咱們看一下具體目標類FinancialAdviser

//================== FinancialAdviser.h ==================

@interface FinancialAdviser : Subject

@end



//================== FinancialAdviser.m ==================

@implementation FinancialAdviser

@end
複製代碼

由於全部的接口的事先已經在Subject類定義好了,因此咱們只需新建一個咱們須要的子類便可(若是有不一樣於父類的操做的話仍是能夠按照本身的方式定義)。

下面咱們看一下觀察者的機制是如何實現的:

FinancialAdviser *fa = [[FinancialAdviser alloc] init];
    
Investor *iv1 = [[Investor alloc] initWithSubject:fa];
    
NSLog(@"====== first advice ========");
[fa setBuyingPrice:1.3];
    
    
Investor *iv2 = [[Investor alloc] initWithSubject:fa];
Investor *iv3 = [[Investor alloc] initWithSubject:fa];

NSLog(@"====== second advice ========");
[fa setBuyingPrice:2.6];
複製代碼

從代碼中能夠看到,咱們最開始向FinancialAdviser(具體目標類)添加了一個具體觀察者類的實例iv1,而後FinancialAdviser的實例fa便通知了全部觀察者(此時的觀察者只有iv1)。

後面咱們繼續向fa添加了iv2iv3後發送通知。此時三個觀察者都收到了消息。

在下面的日至輸出中也能夠看到,內存地址0x600003094c00就是iv10x6000030836800x600003083690就是iv2iv3

====== first advice ========
investor 0x600003094c00 buy stock of price:1.30
====== second advice ========
investor 0x600003094c00 buy stock of price:2.60
investor 0x600003083680 buy stock of price:2.60
investor 0x600003083690 buy stock of price:2.60
複製代碼

下面看一下上面代碼對應的類圖。

代碼對應的類圖

觀察者模式代碼示例類圖

優勢

  • 觀察者模式在觀察目標和觀察者之間創建了一個抽象的耦合。
  • 可實現廣播的,一對多的通訊

缺點

  • 若是一個觀察目標對象有不少直接和間接的觀察者的話,會須要比較多的通訊時間。
  • 須要注意觀察者和觀察目標之間是否有循環引用。

iOS SDK 和 JDK中的應用

  • 在 iOS SDK 中的 KVO 與 NSNotification 是觀察者模式的應用。
  • 在JDK的java.util包中,提供了Observable類以及Observer接口,它們構成了Java語言對觀察者模式的支持。

七. 中介者模式

定義

中介者模式(Mediator Pattern):用一箇中介對象來封裝一系列的對象交互,中介者使各對象之間不須要顯式地相互引用,從而使其耦合鬆散,並且能夠獨立地改變它們之間的交互。

適用場景

系統結構可能會日益變得複雜,對象之間存在大量的相互關聯和調用,系統的總體結構容易變爲網狀結構。在這種狀況下,若是須要修改某一個對象,則可能會要跟蹤和該對象關聯的其餘全部對象,並進行處理。耦合越多,修改的地方就會越多。

若是咱們使用中介者對象,則能夠將系統的網狀結構變成以中介者爲中心的星型結構。中介者承擔了中轉做用和協調做用,簡化了對象之間的交互,並且還能夠給對象間的交互進行進一步的控制。

如今咱們清楚了中介者模式的適用場景,下面看一下中介者模式的成員和類圖。

成員與類圖

成員

中介者模式一共有四個成員:

  1. 抽象中介者(Mediator):抽象中介者定義具體中介者須要實現的接口。
  2. 具體中介者(Concrete Mediator):具體中介者實現抽象中介者定義的接口,承擔多個具體同事類之間的中介者的角色。
  3. 抽象同事類(Colleague):抽象同事類定義具體同事類須要實現的接口。
  4. 具體同事類(Concrete Colleague):具體同事類實現抽象同事類定義的接口。

模式類圖

狀態模式類圖

代碼示例

場景概述

模擬一個多人對話的場景:當一我的發出消息後,另外的那些人能夠收到該消息。

場景分析

假設一共有A,B,C三我的,那麼當A發出消息後,須要分別傳遞給B,C二人。若是三我的直接相互通訊,可能僞代碼會是這樣的:

A sent message to B
A sent message to C
複製代碼

並且隨着人數的增多,代碼行數也會變多,這顯然是不合理的。

所以在這種場景下,咱們須要使用中介者模式,在全部人中間來作一個消息的多路轉發:當A發出消息後,由中介者來發送給B和C:

A sent message to Mediator ;
Mediator sent message to B & C
複製代碼

下面咱們看一下如何用代碼來模擬該場景。

代碼實現

首先咱們建立通話的用戶類User:

//================== User.h ==================

@interface User : NSObject

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

- (void)sendMessage:(NSString *)message;

- (void)receivedMessage:(NSString *)message;

@end



//================== User.m ==================

@implementation User
{
    NSString *_name;
    ChatMediator *_chatMediator;
}

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

- (void)sendMessage:(NSString *)message{
    
    NSLog(@"================");
    NSLog(@"%@ sent message:%@",_name,message);
    [_chatMediator sendMessage:message fromUser:self];
    
}

- (void)receivedMessage:(NSString *)message{
    
    NSLog(@"%@ has received message:%@",_name,message);
}

@end
複製代碼

用戶類在初始化的時候須要傳入中介者的實例,並持有。目的是爲了在後面發送消息的時候把消息轉發給中介者。

另外,用戶類還對外提供了發送消息和接收消息的接口。而在發送消息的方法內部其實調用的是中介者的發送消息的方法(由於中介者持有了全部用戶的實例,所以能夠作多路轉發),具體是如何作的咱們能夠看下中介者類ChatMediator的實現:

//================== ChatMediator.h ==================

@interface ChatMediator : NSObject

- (void)addUser:(User *)user;

- (void)sendMessage:(NSString *)message fromUser:(User *)user;

@end



//================== ChatMediator.m ==================

@implementation ChatMediator
{
    NSMutableArray <User *>*_userList;
}

- (instancetype)init{
    
    self = [super init];
    
    if (self) {
        _userList = [NSMutableArray array];
    }
    return self;
}

- (void)addUser:(User *)user{

    [_userList addObject:user];
}

- (void)sendMessage:(NSString *)message fromUser:(User *)user{
    
    [_userList enumerateObjectsUsingBlock:^(User * _Nonnull iterUser, NSUInteger idx, BOOL * _Nonnull stop) {
        
        if (iterUser != user) {
            [iterUser receivedMessage:message];
        }
    }];
}

@end
複製代碼

中介者類提供了addUser:的方法,所以咱們能夠不斷將用戶添加到這個中介者裏面(能夠看作是註冊行爲或是「加入羣聊」)。在每次加入一個User實例後,都將這個實例添加到中介者持有的這個可變數組裏。因而在未來中介者就能夠經過遍歷數組的方式來作消息的多路轉發,具體實現能夠看sendMessage:fromUser:這個方法。

到如今爲止,用戶類和中介者類都建立好了,咱們看一下消息是如何轉發的:

ChatMediator *cm = [[ChatMediator alloc] init];
    
User *user1 = [[User alloc] initWithName:@"Jack" mediator:cm];
User *user2 = [[User alloc] initWithName:@"Bruce" mediator:cm];
User *user3 = [[User alloc] initWithName:@"Lucy" mediator:cm];
    
[cm addUser:user1];
[cm addUser:user2];
[cm addUser:user3];
    
[user1 sendMessage:@"happy"];
[user2 sendMessage:@"new"];
[user3 sendMessage:@"year"];
複製代碼

從代碼中能夠看到,咱們這裏建立了三個用戶,分別加入到了聊天中介者對象裏。再後面咱們分別讓每一個用戶發送了一條消息。咱們下面經過日至輸出來看一下每一個用戶的消息接收狀況:

[13806:1284059] ================
[13806:1284059] Jack sent message:happy
[13806:1284059] Bruce has received message:happy
[13806:1284059] Lucy has received message:happy
[13806:1284059] ================
[13806:1284059] Bruce sent message:new
[13806:1284059] Jack has received message:new
[13806:1284059] Lucy has received message:new
[13806:1284059] ================
[13806:1284059] Lucy sent message:year
[13806:1284059] Jack has received message:year
[13806:1284059] Bruce has received message:year
複製代碼

下面看一下上面代碼對應的類圖。

代碼對應的類圖

中介者模式代碼示例類圖

優勢

  • 中介者使各對象不須要顯式地相互引用,從而使其耦合鬆散。

缺點

  • 在具體中介者類中包含了同事類之間的交互細節,可能會致使具體中介者類很是複雜,使得其難以維護。

iOS SDK 和 JDK中的應用

  • JDK中的Timer就是中介者類的實現,而配合使用的TimerTask則是同事類的實現。

到這裏設計模式中的行爲型模式就介紹完了,讀者能夠結合UML類圖和demo的代碼來理解每一個設計模式的特色和相互之間的區別,但願讀者能夠有所收穫。

本篇博客的代碼和類圖都保存在個人GitHub庫中:knightsj:object-oriented-design中的 Chapter 2.3

到本篇爲止,面向對象設計系列暫時告一段落,短時間內不會有新的文章出來。讀者朋友們能夠隨時給我提意見或溝通。

本篇已同步到我的博客:傳送門

該系列前面的三篇文章:

參考書籍和教程

相關文章
相關標籤/搜索