iOS 設(shè)計(jì)模式

設(shè)計(jì)模式(Design pattern)是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。使用設(shè)計(jì)模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設(shè)計(jì)模式于己于他人于系統(tǒng)都是多贏的;設(shè)計(jì)模式使代碼編制真正工程化;設(shè)計(jì)模式是軟件工程的基石脈絡(luò),如同大廈的結(jié)構(gòu)一樣。

包含如下設(shè)計(jì)模式的概念和實(shí)際使用:

  1. 原型模式
  2. 工廠方法模式
  3. 抽象工廠模式
  4. 生成器模式
  5. 單例模式
  6. 適配器模式
  7. 橋接模式
  8. 外觀模式
  9. 中介者模式
  10. 觀察者模式
  11. 組合模式
  12. 迭代器模式
  13. 訪問者模式
  14. 裝飾模式
  15. 責(zé)任鏈模式
  16. 模板方法模式
  17. 策略模式
  18. 命令模式

參考書籍《Objective-C 編程之道》:Processon 繪制 UML


針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程

  1. 只要對(duì)象符合客戶端所要求的接口,客戶端就不必在意所使用對(duì)象的確切類型。
  2. 客戶端只知道定義接口的協(xié)議或者抽象類,因此客戶端對(duì)對(duì)象的類一無所知。

定義具有相同接口的類群很重要,因?yàn)槎鄳B(tài)是基于接口的。Objective-C有一種類似的東西叫做協(xié)議,協(xié)議是對(duì)象之間的一種合約,但本身不能實(shí)例化對(duì)象。實(shí)現(xiàn)協(xié)議或者從抽象類繼承,使得對(duì)象共享相同的接口。有幾點(diǎn)好處:

協(xié)議并不定義任何實(shí)現(xiàn),而只申明方法,以確定符合協(xié)議的類的行為。因此協(xié)議只定義了抽象行為的“接口”。實(shí)現(xiàn)協(xié)議的類定義這些方法的實(shí)現(xiàn),以執(zhí)行真正的操作。變更協(xié)議可能會(huì)破壞實(shí)現(xiàn)該協(xié)議的類,可以使用 @optional 指令,將協(xié)議的部分方法變更為“可選的”。

客戶端使用由協(xié)議所定義類型的對(duì)象,有個(gè) Mark 協(xié)議,可以這樣引用

id <Mark> = thisMark;

抽象基類通過生成一些其他子類可以共享的默認(rèn)行為,抽象基類與通常的類相似,只是預(yù)留了一些可以或應(yīng)該由子類重寫的行為。

如果Mark被申明為抽象基類,語法是這樣

Mark *thisMark;

對(duì)象創(chuàng)建

1. 原型模式:

1.1 原型模式是一種非常簡(jiǎn)單的設(shè)計(jì)模式,客戶端知道抽象 Prototype 類。在運(yùn)行時(shí),抽象 Prototype 子類的任何對(duì)象都可以按客戶端的意愿被復(fù)制,因此,無需手工創(chuàng)建就可以制造同一個(gè)類型的多個(gè)實(shí)例。

原型模式

1.2 在Objective-C中這樣實(shí)現(xiàn),在Prototype類(基類)中定義復(fù)制自身的接口,在實(shí)際子類中(ConcretePrototype1、ConcretePrototype2...)實(shí)現(xiàn)該方法

// Prototype.h 文件
// 在抽象基類(Prototype)中定義復(fù)制自身的接口

// Subclassing
- (Prototype *)clone;
- (void)print;


// Prototype.m 文件

// 基類方法,什么也不做
- (Prototype *)clone {
    Prototype *typeSelf = [[Prototype alloc] init];
    return typeSelf;
}

- (void)print {
    NSLog(@"This is = %@ class = [%@]",self,[self class]);
}

// 在ConcretePrototype1類中重寫父類clone方法
- (Prototype *)clone {
    ConcretePrototype1 *typeSelf = [[ConcretePrototype1 alloc] init];
    return typeSelf;
}

// 在客戶端這樣創(chuàng)建對(duì)象
Prototype *type1 = [[ConcretePrototype1 alloc] init];
[type1 print];
Prototype *newType1 = [type1 clone];
[newType1 print];

1.3 何時(shí)實(shí)用原型模式:

  1. 需要?jiǎng)?chuàng)建的對(duì)象應(yīng)獨(dú)立于其他類型與創(chuàng)建方式
  2. 要實(shí)例化的類是在運(yùn)行時(shí)決定的
  3. 不想要與產(chǎn)品層次相對(duì)應(yīng)的工廠層次
  4. 不同類的實(shí)例間的差異僅是狀態(tài)的若干組合。因此復(fù)制相應(yīng)數(shù)量的原型比手工實(shí)例化更加方便
  5. 類不容易創(chuàng)建,比如每個(gè)組件可把其他組件作為子節(jié)點(diǎn)的組合對(duì)象,復(fù)制已有的組合對(duì)象并對(duì)副本進(jìn)行修改會(huì)更加容易

2. 工廠方法

2.1 工廠方法模式:定義創(chuàng)建對(duì)象的接口,讓子類決定實(shí)例化那一個(gè)類,工廠方法使得一個(gè)類的實(shí)例化延遲到其子類。

2.2 舉個(gè)例子:對(duì)象工廠與生產(chǎn)有形產(chǎn)品的真實(shí)工廠類似,例如,制鞋廠生產(chǎn)鞋,手機(jī)工廠生產(chǎn)手機(jī)。比如你讓工廠給你生產(chǎn)些產(chǎn)品。你給它們發(fā)送一個(gè) “生產(chǎn)產(chǎn)品的消息”。制鞋廠和手機(jī)廠都按照相同的“生產(chǎn)產(chǎn)品”的協(xié)議,啟動(dòng)其生產(chǎn)線。過程結(jié)束后,每個(gè)廠家都返回所生產(chǎn)的特定類型的產(chǎn)品。我們把“生產(chǎn)”這個(gè)有魔力的詞稱作為工廠方法,因?yàn)樗敲钌a(chǎn)者(工廠)得到想要產(chǎn)品的方法。

工廠方法模式

2.3 在Objective-C中可以這樣寫:抽象的 Product(產(chǎn)品)定義了工廠方法創(chuàng)建的對(duì)象的接口。ConcreteProduct 實(shí)現(xiàn)了 Product 接口。Creator 定義了返回 Product 對(duì)象的工廠方法。它也可以為工廠方法定義一個(gè)默認(rèn)實(shí)現(xiàn),返回默認(rèn) ConcreteProduct 對(duì)象。Creator 的其他操作可以調(diào)用此工廠方法創(chuàng)建 Product 對(duì)象。ConcreteCreator 是 Creator 的子類。 它重寫了工廠方法,以返回 ConcreteProduct 的實(shí)例。

// 在Creator中定義一個(gè)接口
- (Product *)factoryMethod;

// 返回一個(gè)抽象產(chǎn)品
- (Product *)factoryMethod {
    Product *product = [[Product alloc] init];
    return product;
}

// 在ConcreteCreator中返回實(shí)際產(chǎn)品
- (Product *)factoryMethod {
    ConcreteProduct *product = [[ConcreteProduct alloc] init];
    return product;
}

// 在客戶端這樣創(chuàng)建產(chǎn)品
Product *product = [ConcreteCreator factoryMethod];
// 得到ConcreteCreator對(duì)象了

2.4 如下情形,會(huì)考慮使用工廠方法

  1. 編譯時(shí)無法準(zhǔn)確預(yù)期要?jiǎng)?chuàng)建的對(duì)象的類
  2. 類想讓其子類決定在運(yùn)行時(shí)創(chuàng)建什么
  3. 類有若干輔助類為其子類,而你想將返回哪個(gè)子類這一信息局部化

使用這一模式的一個(gè)常見例子是 Cocoa Touch 框架中的 NSNumber。盡管可以使用常見的 alloc init 兩步創(chuàng)建 NSNumber 實(shí)例,但這沒有什么用。除非使用預(yù)先定義的類工廠方法來創(chuàng)建有意義的實(shí)例。例如 [NSNumber numberWithBool:YES] 消息會(huì)得到 NSNumber 的子類 NSCFBoolean 的一個(gè)實(shí)例。


3. 抽象工廠
  1. 通過對(duì)象組合創(chuàng)建抽象產(chǎn)品
  2. 創(chuàng)建多系列產(chǎn)品
  3. 必須修改父類的接口才能支持新的產(chǎn)品

3.1 抽象工廠模式:提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴對(duì)象的接口,而無需指定它們具體的類或其創(chuàng)建的細(xì)節(jié)??蛻舳伺c從工廠得到的具體對(duì)象之間沒有耦合

抽象工廠模式

4. 生成器

4.1 生成器模式:將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表現(xiàn)分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表現(xiàn)

4.2 Build 是一個(gè)抽象接口,申明了一個(gè) buildPart 方法,該 builder 方法由 ConcreteBuilder 實(shí)現(xiàn),以構(gòu)造實(shí)際產(chǎn)品(Product)。ConcreteBuilder 有個(gè) getResult 方法,向客戶端返回構(gòu)造完畢的 Product,Director 定義了一個(gè) construct 方法,命令 Builder 的實(shí)例去 buildPart。 Director和Builder形成一種聚合關(guān)系。這意味著 Builder 是一個(gè)組成部分,與 Director 結(jié)合。以使整個(gè)模式運(yùn)轉(zhuǎn),但同時(shí),Director 并不負(fù)責(zé) Builder 的生存期。

生成器模式

4.3 何時(shí)實(shí)用生成器模式:

  1. 構(gòu)建復(fù)雜對(duì)象
  2. 以多個(gè)步驟構(gòu)建對(duì)象
  3. 以多種方式構(gòu)建對(duì)象
  4. 在構(gòu)建過程的最后一步返回產(chǎn)品
  5. 專注一個(gè)特定產(chǎn)品

5. 單例

5.1 單例模式:保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)。也是最簡(jiǎn)單的一種模式。

單例模式

5.2 在Objective-C中可以通過GCD的方式來實(shí)現(xiàn)

+ (Singleton *)sharedInstance {

    static Singleton *_instance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 默認(rèn)實(shí)現(xiàn)
        _instance = [[Singleton alloc] initWithSingleton];
        // 支持子類化
        _instance = [[super allocWithZone:NULL] initWithSingleton];
    });
    return _instance;
}

5.3 何時(shí)使用單例模式:

  1. 訪問共享對(duì)象,比如:文件系統(tǒng)、控制類、管理類、配置信息類。在Cocoa Touch中,UIApplication通過單例類設(shè)計(jì)

接口適配

6. 適配器

6.1 適配器模式:將一個(gè)類的接口轉(zhuǎn)換成客戶端希望的另外一個(gè)接口。適配器模式使得由于接口不兼容而不能在一起工作的那些類可以一起工作。用于連接兩種不同種類的對(duì)象,使其毫無問題的協(xié)同工作,有時(shí)稱為包裝器。包括類適配器和對(duì)象適配器。

適配器模式

6.2 包含:類適配器和對(duì)象適配器。代理(Delegate)模式屬于對(duì)象適配器

類適配器

  1. 只針對(duì)單一具體的 Adaptee 類,把 Adaptee 適配到 Target
  2. 易于重載 Adaptee 的行為,因?yàn)槭峭ㄟ^直接的子類化進(jìn)行的適配
  3. 只有一個(gè) Adapter 對(duì)象,無須額外的指針間接訪問 Adaptee

對(duì)象適配器

  1. 可以適配多個(gè) Adaptee 及其子類
  2. 難以重載 Adaptee 的行為,需要借助于子類的對(duì)象而不是 Adaptee 本身
  3. 需要額外的指針以間接訪問 Adaptee 并適配其行為

6.3 使用場(chǎng)景

  1. 已有類的接口與需求不匹配。
  2. 想要一個(gè)可復(fù)用的類,該類能夠同可能帶有不兼容接口的其他類協(xié)作。
  3. 需要適配一個(gè)類的幾個(gè)不同子類,可是讓每一個(gè)子類去子類化一個(gè)類適配器又不現(xiàn)實(shí),那么可以使用對(duì)象適配器(也叫委托)來適配其父類的接口。

6.4 用Objective-C的塊實(shí)現(xiàn)適配器模式。在我們的項(xiàng)目中,有一個(gè)視圖,它允許用戶改變?cè)贑anvasView中繪圖的顏色和線寬。視圖中有3個(gè)滑動(dòng)條,用于改變線色的顏色份量。這一操作涉及幾個(gè)組件---CanvasViewController、PaletteViewController及其滑動(dòng)條。我們需要一個(gè)更好的方案,讓我們能夠把顏色變更的部分復(fù)用到應(yīng)用程序的其他地方。

Block實(shí)現(xiàn)適配器模式

從圖中可看到,定義了一個(gè)叫RGBValuesProvider的塊,簽名為CGFloat ^(CGFloat *red, CGFloat *green, CGFloat *blue)。塊字面量應(yīng)該符合塊簽名,并提供某些處理的實(shí)現(xiàn)。

Command.h 代碼清單

@interface Command : NSObject

{
    @protected
    // 其他一些私有成員變量
}

// 其他屬性...

- (void)execute;

@end

SetStrokeColorCommand.h 代碼清單

#import "Command.h"

typedef void(^RGBValuesProvider)(CGFloat *red, CGFloat *green, CGFloat *blue);

@interface SetStrokeColorCommand : Command

@property (nonatomic, copy) RGBValuesProvider valuesProvider;

- (void)execute;

@end

SetStrokeColorCommand.m 代碼清單

- (void)execute {

    CGFloat redValue = 0.0;
    CGFloat greenValue = 0.0;
    CGFloat blueValue = 0.0;

    // 從塊中取得 RGB 值
    if (_valuesProvider != nil) {
        _valuesProvider(&redValue,&greenValue,&blueValue);
    }
    // 根據(jù) RGB 值創(chuàng)建一個(gè)顏色對(duì)象
    UIColor *color = [UIColor colorWithRed:redValue green:greenValue blue:blueValue alpha:1.0];

    // 把它賦值給當(dāng)前 canvasViewController
    CanvasViewController *vc = [[CoordinatingController sharedInstance] canvasVC];
    vc.view.backgroundColor = color;
}

CommandSlider.h 代碼清單

#import "Command.h"

@interface CommandSlider : UISlider
{
    @protected
    Command *_command;
}

@property (nonatomic, strong) Command *command;

@end

PaletteViewController.m 中的 viewDidLoad方法

SetStrokeColorCommand *colorCommand = [[SetStrokeColorCommand alloc] init];
colorCommand.valuesProvider = ^(CGFloat *red, CGFloat *green, CGFloat *blue) {
    *red = [_redSlider value];
    *green = [_greenSlider value];
    *blue = [_blueSlider value];
};

PaletteViewController.m 用于所有CommandSlider實(shí)例的valueChange:事件處理器

#pragma mark - 
#pragma mark Slider evnet handler
- (void)updateColor:(CommandSlider *)slider {
    [[slider command] execute];
}   

7. 橋接

7.1 橋接模式的目的是把抽象層次結(jié)構(gòu)從其實(shí)現(xiàn)中分離出來,使其能夠獨(dú)立變更。抽象層定義了供客戶端使用的上層的抽象接口。實(shí)現(xiàn)類的引用被封裝于抽象層的實(shí)例中時(shí),橋接就形成了。

橋接模式

7.2 Abstraction是定義了供客戶端使用的上層抽象接口的父接口。它有一個(gè)對(duì)Implementor實(shí)例的引用,Implement定義了實(shí)現(xiàn)類的接口。這個(gè)接口不必跟Abstraction的接口一致。Implement的接口提供基本的操作,而Abstraction的上層操作基于這些基本操作。當(dāng)客戶端向Abstraction的實(shí)例發(fā)送operation消息時(shí),這個(gè)方法向imp發(fā)送operationImp消息。底下的實(shí)際ConcreteImplementator(A或B)將做出相應(yīng)并接受任務(wù)。

7.3 因此想要往系統(tǒng)中添加新的ConcreteImplementator時(shí),所要做的只是為Implementor創(chuàng)建一個(gè)新的實(shí)現(xiàn)類,相應(yīng)operationImp消息并在其中執(zhí)行任何具體的操作。不過,這對(duì)Abstraction方面不會(huì)有任何影響。同樣,如果想修改Abstraction的接口或者創(chuàng)建更細(xì)化的Abstraction類,也能做到不影響橋接的另一頭。

7.4 何時(shí)實(shí)用橋接模式

  1. 不想在抽象與其實(shí)現(xiàn)之間形成固定的綁定關(guān)系(這樣就能在運(yùn)行時(shí)切換實(shí)現(xiàn))
  2. 抽象及其實(shí)現(xiàn)都應(yīng)可以通過子類化獨(dú)立進(jìn)行擴(kuò)展
  3. 對(duì)抽象的實(shí)現(xiàn)進(jìn)行修改不應(yīng)影響客戶端代碼
  4. 如果每個(gè)實(shí)現(xiàn)需要額外的子類以細(xì)化抽象,則說明有必要把它們分成兩個(gè)部分
  5. 想在帶有不同抽象接口的多個(gè)對(duì)象之間共享一個(gè)實(shí)現(xiàn)。

7.5 創(chuàng)建iOS版虛擬仿真器

橋接_仿真器

7.6 ConsoleController和ConsoleEmulator分別是虛擬控制器和仿真器的抽象類。兩個(gè)類有不同的接口。在ConsoleController中封裝一個(gè)對(duì)ConsoleEmulator的引用,是聯(lián)系兩者的唯一方式。因此ConsoleController的實(shí)例可以在一個(gè)抽象層上使用ConsoleEmulator的實(shí)例。這就形成了兩個(gè)不同的類ConsoleController和ConsoleEmulator之間的橋接。ConsoleEmulator為其子類定義了接口,用于處理針對(duì)特定控制臺(tái)OS的底層指令。ConsoleController的setCommand:command消息把它傳遞給內(nèi)嵌的ConsoleEmulator引用。最后,它向這個(gè)引用發(fā)送一個(gè)executeInstructions消息,在仿真器中執(zhí)行任何已加載的指令。

7.7 ConsoleController類層次結(jié)構(gòu)代表對(duì)ConsoleEmulator類層次結(jié)構(gòu)的任何“實(shí)現(xiàn)”的一種抽象。抽象類層次結(jié)構(gòu)提供了一層抽象,形成一個(gè)對(duì)任何兼容ConsoleEmulator的虛擬控制層。具體的ConsoleController只能通過在父類中定義的底層setCommand:方法,與橋接的另一端的仿真器進(jìn)行交流。在這種配置中這個(gè)方法不應(yīng)被子類重載,因?yàn)檫@是一個(gè)讓父類與細(xì)化的控制器之間進(jìn)行通訊的接口。如果在仿真器的一側(cè)發(fā)生變更,對(duì)左側(cè)的任何控制器都將毫無影響,反之亦然。

代碼清單 ConsoleCommands.h

typedef enum {
    kConsoleCommandUp,
    kConsoleCommandDown,
    kConsoleCommandLeft,
    kConsoleCommandRight,
    kConsoleCommandSelect,
    kConsoleCommandStart,
    kConsoleCommandAction,
    kConsoleCommandAction2, 
} ConsoleCommand;

上、下、左、右、選擇、開始、動(dòng)作1和動(dòng)作2作為通用命令,定義一組enum。要是將來想擴(kuò)展命令列表,以支持更復(fù)雜的模擬器,都不會(huì)破壞任何一邊的設(shè)計(jì)。

代碼清單 ConsoleEmulator.h

#import "ConsoleCommands.h"

@interface ConsoleEmulator : NSObject

- (void)loadInstructionsForCommand:(ConsoleCommand)command;
- (void)executeInstructions;

// 其他行為和屬性

@end

代碼清單 GameBoyEmulator.h

@interface GameBoyEmulator : ConsoleEmulator

// 從抽象類重載的行為
- (void)loadInstructionsForCommand:(ConsoleCommand)command;
- (void)executeInstructions;

@end

GameBoyEmulator和GameGearEmulator都是ConsoleEmulator的子類。他們重載抽象方法,并提供自己平臺(tái)的特定行為。

代碼清單 ConsoleController.h

#import "ConsoleEmulator.h"

@interface ConsoleController : NSObject

@property (nonatomic, strong) ConsoleEmulator *emulator;

- (void)setCommand:(ConsoleCommand)command;

// 其他行為和屬性

@end

ConsoleController是整個(gè)虛擬控制器類層次的起點(diǎn),它以emulator_保持著ConsoleEmulator實(shí)例的一個(gè)內(nèi)部引用。它也定義了一個(gè)setCommand:command方法,供其子類用預(yù)先定義的命令類型輸入命令。

代碼清單 ConsoleController.m

- (void)setCommand:(ConsoleCommand)command {
    [_emulator loadInstructionsForCommand:command];
    [_emulator executeInstructions];
}

到此,虛擬控制器與仿真器的基本橋接就完成了?,F(xiàn)在要開始創(chuàng)建第一個(gè)虛擬控制器TouchConsoleController,以形成多點(diǎn)觸摸屏與隱藏在視圖之后具體仿真器之間的接口。

代碼清單 TouchConsoleController.h

@interface TouchConsoleController : ConsoleController

- (void)up;
- (void)down;
- (void)left;
- (void)right;
- (void)select;
- (void)start;
- (void)action1;
- (void)action2;

@end

代碼清單 TouchConsoleController.m

@implementation TouchConsoleController

- (void)up {
    [super setCommand:kConsoleCommandUp];
}
- (void)down {
    [super setCommand:kConsoleCommandDown];
}
- (void)left {
    [super setCommand:kConsoleCommandLeft];
}
- (void)right {
    [super setCommand:kConsoleCommandRight];
}
- (void)select {
    [super setCommand:kConsoleCommandSelect];
}
- (void)start {
    [super setCommand:kConsoleCommandStart];
}
- (void)action1 {
    [super setCommand:kConsoleCommandAction];
}
- (void)action2 {
    [super setCommand:kConsoleCommandAction2];
}

@end

客戶端代碼 viewDidLoad方法

// ConsoleEmulator *emulator = [[GameBoyEmulator alloc] init];
ConsoleEmulator *emulator = [[GameGearEmulator alloc] init];

TouchConsoleController *control = [[TouchConsoleController alloc] init];
control.emulator = emulator;
[control up];  // left、right、action1.....等命令

8. 外觀

8.1 外觀模式:為子系統(tǒng)中一組不同的接口提供統(tǒng)一的接口。外觀定義了上層接口,通過降低復(fù)雜度和隱藏子系統(tǒng)間的通信及依存關(guān)系,讓子系統(tǒng)更易于使用。

外觀模式

8.2 可以看到整個(gè)出租車服務(wù)作為一個(gè)封閉系統(tǒng),包括出租車司機(jī)、一輛車和一臺(tái)計(jì)價(jià)器。同系統(tǒng)交互的唯一途徑是通過CabDriver中定義的接口driveToLocation:x。一旦乘客向出租車司機(jī)發(fā)出driveToLocation:消息,CabDriver就會(huì)收到這個(gè)消息。司機(jī)需要操作兩個(gè)子系統(tǒng)---Taximeter和Car。CabDriver先會(huì)啟動(dòng)(start)Taximeter,讓它開始計(jì)價(jià),然后司機(jī)對(duì)汽車會(huì)松剎車(releaseBrakes)、換擋(changeGears)...停止(stop),Taximeter,結(jié)束行程。一切都發(fā)生于發(fā)送CabDriver的一個(gè)簡(jiǎn)單的driveToLocation:x命令之中。無論這兩個(gè)子系統(tǒng)有多么復(fù)雜,他們隱藏于乘客的視線之外。因此CabDriver是在為出租車子系統(tǒng)的其他復(fù)雜接口提供一個(gè)簡(jiǎn)化的接口。

代碼清單 Car.h

@interface Car : NSObject

- (void)releaseBrakes;
- (void)changeGears;
- (void)pressAccelerator;
- (void)pressBrakes;
- (void)releaseAccelerator;
- (void)stop;

@end

代碼清單 Taximeter.h

@interface Taximeter : NSObject

- (void)start;
- (void)stop;

@end

代碼清單 CabDriver.h

@interface CabDriver : NSObject

- (void)driveToLocation:(CGPoint)x;

@end

代碼清單 CabDriver.m

- (void)driveToLocation:(CGPoint)x {
    // ...

    // 啟動(dòng)計(jì)價(jià)器
    Taximeter *meter = [[Taximeter alloc] init];
    [meter start];

    // 操作車輛,直到抵達(dá)位置 x
    Car *car = [[Car alloc] init];
    [car releaseBrakes];
    [car changeGears];
    [car pressAccelerator];

    // ......

    // 當(dāng)?shù)竭_(dá)了位置 x,就停下車和計(jì)價(jià)器
    [car releaseAccelerator];
    [car pressBrakes];
    [meter stop];
}

代碼清單,客戶端代碼 viewDidLoad方法

CabDriver *driver = [[CabDriver alloc] init];
[driver driveToLocation:CGPointMake(100.0, 100.0)];

8.3 使用場(chǎng)景

  1. 子系統(tǒng)正逐漸變得復(fù)雜,應(yīng)用模式的過程中演化出許多類??梢允褂猛庥^為這些子系統(tǒng)類提供一個(gè)簡(jiǎn)單的接口。
  2. 可以使用外觀對(duì)子系統(tǒng)進(jìn)行分層,每個(gè)子系統(tǒng)級(jí)別有一個(gè)外觀作為入口點(diǎn)。讓它們通過其外觀進(jìn)行通信,可以簡(jiǎn)化它們的依存關(guān)系。

對(duì)象去耦

9. 中介者

9.1 中介者模式是用一個(gè)對(duì)象來封裝一些列對(duì)象的交互方式。中介者使各對(duì)象不需要顯示地相互引用,從而使其耦合松散,而且可以獨(dú)立的改變它們之間的交互。

中介者模式

9.2 抽象的 Mediator 定義了用于同 Colleague 交互的一般行為,典型的同事(colleague)是以明確定義的方式進(jìn)行相互通信的對(duì)象,并且彼此緊密依存,ConcreteMediator 為 ConcreteColleague定義了更加具體的行為。如果應(yīng)用程序只需要一個(gè)中介者,有時(shí)抽象 Mediator 可以省略。

9.3 使用場(chǎng)景

  1. 對(duì)象間的交互雖定義明確然而非常復(fù)雜,導(dǎo)致一組對(duì)象彼此相互依賴而且難以理解。
  2. 因?yàn)閷?duì)象引用了許多其他對(duì)象并與其通訊,導(dǎo)致對(duì)象難以復(fù)用
  3. 想要定制一個(gè)分布在多個(gè)類中的邏輯與行為,又不想生成太多子類
10. 觀察者

10.1 觀察者模式:定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都得到通知并被自動(dòng)更行。

觀察者模式

10.1 觀察者模式是一種發(fā)布-訂閱模型。Observer從Subject訂閱通知。ConcreteObserver實(shí)現(xiàn)抽象Observer并重載update方法。一旦Subject的實(shí)例需要通知Observer任何新的變更,Subject會(huì)發(fā)送update消息并通知存儲(chǔ)內(nèi)部列表中所有注冊(cè)的Observer。在ConcreteObserver的update方法的實(shí)例實(shí)現(xiàn)中,Subject內(nèi)部狀態(tài)可被取得并在以后進(jìn)行處理。

10.2 Subject提供注冊(cè)與取消注冊(cè)的方法,任何實(shí)現(xiàn)了Observer協(xié)議而且想要處理update消息的對(duì)象,都可以進(jìn)行注冊(cè)或取消注冊(cè)。當(dāng)Subject的實(shí)例發(fā)生變更時(shí),它會(huì)想自己發(fā)送notify消息。notify方法里有個(gè)算法,定義了如何向已注冊(cè)的觀察者廣播update消息。

10.3 何時(shí)使用觀察者模式

  1. 有兩種抽象類型相互依賴。將它們封裝在各自的對(duì)象中,就可以對(duì)它們單獨(dú)進(jìn)行改變和復(fù)用。
  2. 對(duì)一個(gè)對(duì)象的改變需要同時(shí)改變其他對(duì)象,而不知道具體有多少對(duì)象有待改變
  3. 一個(gè)對(duì)象必須通知其他對(duì)象,而它又不需要知道其他對(duì)象是什么

10.4 在Cocoa Touch框架中使用觀察者模式:通知和鍵-值觀察

10.4.1 通知

Cocoa Touch框架使用NSNotificationCenter和NSNotification對(duì)象實(shí)現(xiàn)了一對(duì)多的發(fā)布-訂閱模型。它們?cè)试S主題與觀察者以一種松耦合的方式通信。兩者在通信時(shí)對(duì)另一方無需多少了解。

主題要通知其他對(duì)象時(shí),需要?jiǎng)?chuàng)建一個(gè)可通過全局的名字來識(shí)別的通知對(duì)象,然后把它投遞到通知中心。通知中心查明特定的觀察者,然后通過消息把通知發(fā)送給它們。

一旦創(chuàng)建了通知,就用它作為[notificationCenter postNotification:notification]消息調(diào)用的參數(shù),投遞到通知中心。通過向NSNotificationCenter類發(fā)送defaultCenter消息,可以得到NSNotificationCenter實(shí)例的引用。每個(gè)進(jìn)程只有一個(gè)默認(rèn)的通知中心,所以默認(rèn)的NSNotificationCenter是個(gè)單例對(duì)象。defaultCenter是返回應(yīng)用程序中NSNotificationCenter的唯一默認(rèn)實(shí)例的工廠方法

模型可以這樣構(gòu)造一個(gè)通知然后投遞到通知中心

NSNotification *notification = [NSNotification notificationWithName:@"data changes" object:self];
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];

// 發(fā)送通知
[notificationCenter postNotification:notification];

// 訂閱通知
[notificationCenter addObserver:self selector:@selector(update:) name:@"data changes" object:subject];

// 刪除通知
[notificationCenter removeObserver:self name:@"data changes" object:nil];

10.4.2 鍵-值觀察

是一種通知機(jī)制,它使對(duì)象能夠在其他對(duì)象的屬性發(fā)生更改時(shí)獲得通知。在觀察對(duì)象和被觀察對(duì)象之間建立了聯(lián)系。當(dāng)被觀察對(duì)象屬性的值發(fā)生改變時(shí),會(huì)想觀察者發(fā)送一對(duì)一的通知。

這一機(jī)制基于NSKeyValueObserving非正式協(xié)議,Cocoa通過這個(gè)協(xié)議為所有遵守協(xié)議的對(duì)象提供了一種自動(dòng)化的屬性觀察能力。要實(shí)現(xiàn)自動(dòng)觀察,參與鍵-值觀察(Key-Value Observing,KVO)的對(duì)象需要符合鍵-值編碼(KVC)的要求,并且需要符合KVC的存儲(chǔ)方法。KVC基于有關(guān)非正式協(xié)議,通過存儲(chǔ)對(duì)象屬性實(shí)現(xiàn)自動(dòng)觀察。也可以使用NSKeyValueObserving的方法和相關(guān)范疇來實(shí)現(xiàn)手動(dòng)的觀察者通知。

// 監(jiān)聽Person實(shí)例對(duì)象(person)的name屬性變化
[person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil]

// 然后實(shí)現(xiàn)該方法就可以得到屬性的變更通知
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"keyPath = %@",keyPath);
    NSLog(@"change = %@",change);
}

10.4 通知和鍵-值觀察的主要卻別

通知 鍵-值觀察
一個(gè)中心對(duì)象為所有觀察者提供變更通知 被觀察的對(duì)象直接向觀察者發(fā)送通知
主要從廣義上關(guān)注程序事件 綁定于特定對(duì)象屬性的值

抽象集合

11. 組合

11.1 組合模式:將對(duì)象組合成樹形結(jié)構(gòu)以表示"部分-整體"的層次結(jié)構(gòu)。組合使得用戶對(duì)單個(gè)對(duì)象和組合對(duì)象的使用具有一致性

組合模式

基類接口是定義了Leaf類和Composite類的共同操作的Component

每個(gè)節(jié)點(diǎn)代表一個(gè)葉節(jié)點(diǎn)或組合體節(jié)點(diǎn)。Leaf節(jié)點(diǎn)與Composite節(jié)點(diǎn)的主要區(qū)別在于,Leaf節(jié)點(diǎn)不包含同類型的子節(jié)點(diǎn),而Composite則包含。Composite包含同一類型的子節(jié)點(diǎn)。由于Leaf類與Composite類有同樣的接口,任何對(duì)Component類型的操作也能安全地應(yīng)用到Leaf和Composite??蛻舳司筒恍枰鶕?jù)確切類型的is-else語句。

Composite需要方法來管理子節(jié)點(diǎn),比如add:component和remove:component。因?yàn)長(zhǎng)eaf和Composize有共同的接口,這些方法必須也是接口的一部分。而向Leaf對(duì)象發(fā)送組合體操作消息則沒有意義,也不起作用,只有默認(rèn)的實(shí)現(xiàn)。

11.2 使用場(chǎng)景

  1. 想獲得對(duì)象抽象的樹形表示(部分-整體層次結(jié)構(gòu))
  2. 想讓客戶端統(tǒng)一處理組合結(jié)構(gòu)中的所有對(duì)象

11.3 在Cocoa Touch框架中使用組合模式

組合模式

Mark協(xié)議是Dot、Vertex、Stroke類型的基類型,這樣它們具有相同的接口。Dot的實(shí)例可以畫在視圖上,而Stroke的子節(jié)點(diǎn)Vertext對(duì)象只用來幫助在同一線條中把線連接起來。

Vertex只實(shí)現(xiàn)了location屬性。Dot子類化Vertext并增加color與size屬性,因?yàn)閂ertex不需要color和size而Dot需要。在運(yùn)行時(shí)aStroke可以包含aDot或aVertex對(duì)象。因此Stroke對(duì)象既可以是各種Mark的父節(jié)點(diǎn),也可以是由Vertex對(duì)象構(gòu)成的真正的線條組合,作為一個(gè)整體繪制在屏幕上。

代碼清單 Mark.h


@protocol Mark <NSObject>

@property (nonatomic, strong) UIColor *color;
@property (nonatomic, assign) CGFloat size;
@property (nonatomic, assign) CGPoint location;
@property (nonatomic, readonly) NSUInteger count;       // 子節(jié)點(diǎn)的個(gè)數(shù)
@property (nonatomic, readonly) id<Mark>lastChild;

- (id)copy;
- (void)addMark:(id<Mark>)mark;
- (void)removeMark:(id<Mark>)mark;
- (id<Mark>)childMarkAtIndex:(NSUInteger)index;

- (void)drawWithContext:(CGContextRef)context;

@end

代碼清單 Vertex.h


@interface Vertex : NSObject <Mark,NSCopying>

{
    @protected
    CGPoint location_;
}

@property (nonatomic, strong) UIColor *color;
@property (nonatomic, assign) CGFloat size;
@property (nonatomic, assign) CGPoint location;
@property (nonatomic, readonly) NSUInteger count;       // 子節(jié)點(diǎn)的個(gè)數(shù)
@property (nonatomic, readonly) id<Mark>lastChild;

- (id)initWithLocation:(CGPoint)location;
- (void)addMark:(id<Mark>)mark;
- (void)removeMark:(id<Mark>)mark;
- (id<Mark>)childMarkAtIndex:(NSUInteger)index;

- (void)drawWithContext:(CGContextRef)context;

@end


代碼清單 Vertex.m


@synthesize location = location_;
@dynamic color,size;

- (id)initWithLocation:(CGPoint)location {
    if (self = [super init]) {
        [self setLocation:location];
    }
    return self;
}

// 默認(rèn)屬性什么也不做
- (void)setColor:(UIColor *)color {}
- (UIColor *)color {return nil;}
- (void)setSize:(CGFloat)size {}
- (CGFloat)size {return 0.0;}

// Mark 操作什么也不做
- (void)addMark:(id<Mark>)mark {}
- (void)removeMark:(id<Mark>)mark {}
- (id<Mark>)childMarkAtIndex:(NSUInteger)index {return nil;}
- (id<Mark>)lastChild {return nil;}

// 繪圖,一個(gè)頂點(diǎn)
- (void)drawWithContext:(CGContextRef)context {
    CGFloat x = self.location.x;
    CGFloat y = self.location.y;
    
    CGContextAddLineToPoint(context, x, y);
}

    #pragma mark -
    #pragma mark - NSCopying method
// 此方法需要實(shí)現(xiàn),以支持備忘錄
- (id)copyWithZone:(NSZone *)zone {
    Vertex *vertexCopy = [[[self class] allocWithZone:zone] initWithLocation:location_];
    return vertexCopy;
}

代碼清單 Dot.h


@interface Dot : Vertex

{
    @private
    UIColor *color_;
    CGFloat size_;
}

@property (nonatomic, strong) UIColor *color;
@property (nonatomic, assign) CGFloat size;

- (void)drawWithContext:(CGContextRef)context;

@end

代碼清單 Dot.m


@synthesize size = size_, color = color_;

- (void)drawWithContext:(CGContextRef)context {
    CGFloat x = self.location.x;
    CGFloat y = self.location.y;
    CGFloat frameSize = self.size;
    CGRect frame = CGRectMake(x, y, frameSize, frameSize);
    
    CGContextSetFillColorWithColor(context, [self color].CGColor);
    CGContextFillEllipseInRect(context, frame);
}

        #pragma mark - 
        #pragma mark - NSCopying method
- (id)copyWithZone:(NSZone *)zone {
    Dot *dotCopy = [[[self class] allocWithZone:zone] initWithLocation:location_];
    // 復(fù)制 color
    [dotCopy setColor:[UIColor colorWithCGColor:[color_ CGColor]]];
    // 復(fù)制 size
    [dotCopy setSize:size_];
    return dotCopy;
}


代碼清單 Stroke.h


@interface Stroke : NSObject <Mark,NSCopying>

{
    @private
    UIColor *color_;
    CGFloat size_;
    NSMutableArray *children_;
}

@property (nonatomic, strong) UIColor *color;
@property (nonatomic, assign) CGFloat size;
@property (nonatomic, assign) CGPoint location;
@property (nonatomic, readonly) NSUInteger count;
@property (nonatomic, readonly) id<Mark>lastChild;

- (void)addMark:(id<Mark>)mark;
- (void)removeMark:(id<Mark>)mark;
- (id<Mark>)childMarkAtIndex:(NSUInteger)index;

- (void)drawWithContext:(CGContextRef)context;

@end


代碼清單 Stroke.m


@implementation Stroke

@synthesize color = color_,size = size_;
@dynamic location;

- (id)init {
    if (self = [super init]) {
        children_ = [[NSMutableArray alloc] init];
    }
    return self;
}

- (void)setLocation:(CGPoint)location {
    // 不做任何位置設(shè)定
}
- (CGPoint)location {
    // 返回第一個(gè)節(jié)點(diǎn)的位置
    if ([children_ count] > 0) {
        id<Mark>child = children_[0];
        return [child location];
    }
    // 否則,返回原點(diǎn)
    return CGPointZero;
}

- (void)addMark:(id<Mark>)mark {
    [children_ addObject:mark];
}
- (void)removeMark:(id<Mark>)mark {
    // 如果 mark 在這一層,將其移除并返回
    // 否則,讓每個(gè)自己點(diǎn)去找它
    if ([children_ containsObject:mark]) {
        [children_ removeObject:mark];
    }
    else {
        [children_ makeObjectsPerformSelector:@selector(removeMark:) withObject:mark];
    }
}
- (id<Mark>)childMarkAtIndex:(NSUInteger)index {
    if (index >= [children_ count]) {
        return nil;
    }
    return children_[index];
}

// 返回最后子節(jié)點(diǎn)的便利方法
- (id<Mark>)lastChild {
    return [children_ lastObject];
}
// 返回子節(jié)點(diǎn)個(gè)數(shù)
- (NSUInteger)count {
    return [children_ count];
}

- (void)drawWithContext:(CGContextRef)context {
    CGContextMoveToPoint(context, self.location.x, self.location.y);
    for (id<Mark>mark in children_) {
        [mark drawWithContext:context];
    }
    CGContextSetStrokeColorWithColor(context, [self color].CGColor);
}

        #pragma mark -
        #pragma mark - NSCopying method
- (id)copyWithZone:(NSZone *)zone {
    Stroke *strokeCopy = [[[self class] allocWithZone:zone] init];
    // 復(fù)制 color
    [strokeCopy setColor:[UIColor colorWithCGColor:[color_ CGColor]]];
    // 復(fù)制 size
    [strokeCopy setSize:size_];
    // 復(fù)制 children
    for (id<Mark>child in children_) {
        id<Mark>childCopy = [child copy];
        [strokeCopy addMark:childCopy];
    }
    return strokeCopy;
}

Stroke用自己的children_作為NSMutableArray的實(shí)例,來保存Mark子節(jié)點(diǎn)。

代碼清單,客戶端構(gòu)造Mark組合體


Dot *singleDot = [[Dot alloc] initWithLocation:thisPoint];
Vertex *vertex = [[Vertex alloc] initWithLocation:thisPoint];

Stroke *newStroke = [[Stroke alloc] init];

[newStroke addMark:singleDot];
[newStroke addMark:vertex];

Stroke *parentStroke = [[Stroke alloc] init];
[parentStroke addMark:newStroke];
[parentStroke addMark:singleDot];

單個(gè)Dot對(duì)象可被添加到parentStroke作為葉節(jié)點(diǎn)。parentStroke也可以接受組合體Stroke對(duì)象,組合體Stroke對(duì)象為了讓繪圖算法繪制相連的線,管理者自己的Vertex子節(jié)點(diǎn)。


12. 迭代器

12.1 迭代器提供了一種順序訪問聚合對(duì)象(集合)中元素的方法,而無需暴露結(jié)構(gòu)的底層表示和細(xì)節(jié)。

迭代器模式

12.2 List定義了修改集合以及返回集合中元素個(gè)數(shù)的方法。ListIterator保持一個(gè)對(duì)List對(duì)象的引用,以便迭代器遍歷結(jié)構(gòu)中的元素并將其返回。ListIterator定義了讓客戶端從迭代過程中訪問下一項(xiàng)的方法。迭代器有個(gè)內(nèi)部的index_變量,記錄集合中的當(dāng)前位置。

12.3 在Cocoa Touch框架中使用迭代器模式,通過"枚舉器/枚舉"改寫了迭代器模式。

通過NSEnumerator來枚舉NSArray、NSDictionary和NSSet對(duì)象中的元素。NSEnumerator本身是個(gè)抽象類。它依靠幾個(gè)工廠方法,如:objectEnumerator或keyEnumerator,來創(chuàng)建并返回相應(yīng)的具體枚舉器對(duì)象??蛻舳擞梅祷氐拿杜e器對(duì)象遍歷集合中的元素,

NSArray *array = @[@"one",@"two",@"three"];
NSEnumerator *itemEnumerator = [array objectEnumerator];

NSString *item;
while (item = [itemEnumerator nextObject]) {
    // 對(duì) item作處理 
}

基于塊的枚舉,更加方便

[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    
    NSString *item = obj;
    if ([item isEqualToString:@"two"]) {
        // 終止枚舉
        *stop = YES;
    }
}];

12.2 何時(shí)實(shí)用迭代器模式

  1. 需要訪問組合對(duì)象的內(nèi)容,而又不暴露其內(nèi)部表示
  2. 需要通過多種方式遍歷組合對(duì)象
  3. 需要提供一個(gè)統(tǒng)一的接口,用來遍歷各種類型的組合對(duì)象

行為擴(kuò)展

13. 訪問者

13.1 訪問者模式:表示一個(gè)作用于某對(duì)象結(jié)構(gòu)中的各元素的操作。它讓我們可以在不改變各元素的類的前提下定義作用于這些元素的新操作。

訪問者模式

13.2 訪問者模式涉及兩個(gè)關(guān)鍵角色(或者組件):訪問者和它訪問的元素。元素可以是任何對(duì)象,但通常是"部分-整體"結(jié)構(gòu)中的結(jié)點(diǎn)(組合模式)。部分-整體結(jié)構(gòu)包含組合體與葉節(jié)點(diǎn),或者任何其他復(fù)雜的對(duì)象結(jié)構(gòu)。元素本身不僅限于這些種類的結(jié)構(gòu)。訪問者知道復(fù)雜結(jié)構(gòu)中的每個(gè)元素,可以訪問每個(gè)元素的結(jié)點(diǎn),并根據(jù)元素的特征、屬性或操作,執(zhí)行任何操作。

13.3 Visitor協(xié)議聲明了兩個(gè)很像的 visit 方法,用于訪問和處理各種Element類型的對(duì)象。ConcreteVisitor(1或2)實(shí)現(xiàn)這一協(xié)議及其抽象方法。 visit 的操作定義了針對(duì)特定Element類型的適當(dāng)操作。Client創(chuàng)建ConcreteVisit(1或2)的對(duì)象,并將其傳給一個(gè)Element對(duì)象結(jié)構(gòu)。Element對(duì)象結(jié)構(gòu)中有一個(gè)方法接受一般化的Visitor類型。繼承Element的類中,所有acceptVisitor方法中的操作幾乎一樣,只有一條語句,讓Visitor對(duì)象訪問發(fā)起調(diào)用的具體Element對(duì)象。實(shí)際使用的 visit 消息,定義在每個(gè)具體Element類中,這是具體Element類之間的唯一不同之處。每當(dāng)把a(bǔ)cceptVisitor:消息傳給Element結(jié)構(gòu),這個(gè)消息就會(huì)被轉(zhuǎn)發(fā)給每個(gè)結(jié)點(diǎn)。在運(yùn)行時(shí)確定Element對(duì)象的實(shí)際類型,再根據(jù)實(shí)際類型決定該調(diào)用哪個(gè)visit*方法

13.4 何時(shí)實(shí)用訪問者模式

  1. 一個(gè)復(fù)雜的對(duì)象結(jié)構(gòu)包含很多其他對(duì)象,它們有不同的接口(比如組合體),但是想對(duì)這些對(duì)象實(shí)施一些依賴于其具體類型的操作。
  2. 需要對(duì)一個(gè)組合結(jié)構(gòu)中的對(duì)象進(jìn)行很多不相關(guān)的操作,但是不想讓這些操作“污染”這些對(duì)象的類??梢詫⑾嚓P(guān)的操作集中起來,定義在一個(gè)訪問者類中,并在需要在訪問者中定義的操作時(shí)使用它
  3. 定義復(fù)雜結(jié)構(gòu)的類很少做修改,但經(jīng)常需要向其添加新的操作。

14. 裝飾

14.1 裝飾模式:動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就擴(kuò)展功能來說,裝飾模式相比生成子類更為靈活。

裝飾模式

14.2 標(biāo)準(zhǔn)的裝飾模式包括一個(gè)抽象Component父類,它為其他具體組件(component)聲明一些操作。抽象的Component類可被細(xì)化為另一個(gè)叫做Decorator的抽象類。Decorator包含了另一個(gè)Component的引用。ConcreteDecorator為其他Component或Decorator定義了幾個(gè)擴(kuò)展行為。并且會(huì)在自己的操作中執(zhí)行內(nèi)嵌的Component操作。

14.3 Component定義了一些抽象操作,其具體類將進(jìn)行重載以實(shí)現(xiàn)自己特定的操作。Decorator是一個(gè)抽象類,它通過將一個(gè)Component或Decorator內(nèi)簽到Decorator對(duì)象,定義了擴(kuò)展這個(gè)Component的實(shí)例的“裝飾性”行為。默認(rèn)的operation方法只是想內(nèi)嵌的component發(fā)送一個(gè)消息。ConcreteDecoratorA和ConcreteDecoratorB重載父類的operation,通過super把自己增加的行為擴(kuò)展給component的operation。如果只需要向component添加一種職責(zé),那么就可以省掉抽象的Decorator類,讓ConcreteDecorator直接把請(qǐng)求轉(zhuǎn)發(fā)給component。

14.4 何時(shí)實(shí)用裝飾模式

  1. 想要在不影響其他對(duì)象的情況下,以動(dòng)態(tài)、透明的方法給單個(gè)對(duì)象添加職責(zé)
  2. 想要擴(kuò)展一個(gè)類的行為,卻做不到。類定義可能被隱藏,無法進(jìn)行子類化;或者,對(duì)類的么個(gè)行為的擴(kuò)展,為支持每種功能組合,將產(chǎn)生大量的子類
  3. 對(duì)類的職責(zé)的擴(kuò)展是可選的
15. 責(zé)任鏈

15.1 責(zé)任鏈模式:使多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求,從而避免請(qǐng)求的發(fā)送者和接收者之間發(fā)生耦合。此模式將這些對(duì)象連成一條鏈,并沿著這條鏈傳遞請(qǐng)求,直到有一個(gè)對(duì)象處理它為止

[站外圖片上傳中...(image-e9d563-1520217476674)]

15.2 責(zé)任鏈模式的主要思想是,對(duì)象引用了同一類型的另一個(gè)對(duì)象,形成一條鏈。鏈中的每個(gè)對(duì)象實(shí)現(xiàn)了同樣的方法,處理對(duì)鏈中第一個(gè)對(duì)象發(fā)起的同一個(gè)請(qǐng)求。如果一個(gè)對(duì)象不知道如何處理請(qǐng)求,它就把請(qǐng)求傳給下一個(gè)相應(yīng)器(即successor)。

15.3 Handler是上層抽象類,定義了一個(gè)方法---handleRequest,處理它知道如何處理的請(qǐng)求對(duì)象。ConcreteHandler1和ConcreteHandler2實(shí)現(xiàn)了handleRequest方法,來處理它們認(rèn)識(shí)的請(qǐng)求對(duì)象。Handler也有一個(gè)指向另一個(gè)同類型實(shí)例的引用,即successor。當(dāng)調(diào)用Handler實(shí)例的handleRequest消息時(shí),如果這個(gè)實(shí)例不知道如何處理請(qǐng)求,它會(huì)用同樣的消息把請(qǐng)求轉(zhuǎn)發(fā)給successor。如果successor可以處理,就行了;否則,他就把請(qǐng)求傳給下一個(gè)successor(如果有的話)。這個(gè)過程會(huì)一直進(jìn)行下去,直到請(qǐng)求被傳到鏈中的最后一個(gè)successor。

15.4 在RPG游戲中使用責(zé)任鏈模式

RPG責(zé)任鏈模式

Avatar、MetalArmor和CrystalShield是AttackHandler的子類。AttackHandler定義了一個(gè)方法---handleAttack:attack,該方法的默認(rèn)行為是把攻擊傳給另一個(gè)AttackHandler的引用,即成員變量nextAttackHandler_。子類重載這個(gè)方法,對(duì)攻擊提供實(shí)際的響應(yīng)。如果AttackHandler不知道如何響應(yīng)一個(gè)攻擊,那么就使用[super handleAttack:attack]消息,把它轉(zhuǎn)發(fā)給super,這樣super中的默認(rèn)實(shí)現(xiàn)就會(huì)把攻擊沿著鏈傳下去。

代碼清單 AttackHandler.h

#import "Attack.h"

@interface AttackHandler : NSObject

{
    @private
    AttackHandler *nextAttackHandler_;
}

@property (nonatomic, strong) AttackHandler *nextAttackHandler;

- (void)handleAttack:(Attack *)attack;

@end

代碼清單 AttackHandler.m

@implementation AttackHandler

@synthesize nextAttackHandler = nextAttackHandler_;

- (void)handleAttack:(Attack *)attack {
    [nextAttackHandler_ handleAttack:attack];
}

@end

代碼清單 MetalArmor.h

@interface MetalArmor : AttackHandler

// 重載的方法
- (void)handleAttack:(Attack *)attack;

@end

代碼清單 MetalArmor.m

- (void)handleAttack:(Attack *)attack {
    if ([attack isKindOfClass:[SwordAttack class]]) {
        // 攻擊沒有通過這個(gè)盔甲
        NSLog(@"No damage from a sword attack");
    }
    else {
        NSLog(@"I don't know this attack: %@",[self class]);
        [super handleAttack:attack];
    }
}

代碼清單 CrystalShield.m

- (void)handleAttack:(Attack *)attack {
    if ([attack isKindOfClass:[MagicFireAttack class]]) {
        // 攻擊沒有通過這個(gè)盾牌
        NSLog(@"No damage from a magic fire attack");
    }
    else {
        NSLog(@"I don't know this attack: %@",[self class]);
        [super handleAttack:attack];
    }
}

代碼清單 Avatar.m

- (void)handleAttack:(Attack *)attack {
    // 當(dāng)攻擊達(dá)到這里時(shí),我就被擊中了
    // 實(shí)際損傷的點(diǎn)數(shù)取決于攻擊的類型
    NSLog(@"Oh! I'm hit with a %@",[attack class]);
}

代碼清單,客戶端代碼

 // 創(chuàng)建新的人物
AttackHandler *avatar = [[Avatar alloc] init];

// 讓它穿上金屬盔甲
AttackHandler *metalArmoredAvatar = [[MetalArmor alloc] init];
[metalArmoredAvatar setNextAttackHandler:avatar];

// 然后給金屬盔甲中的人物添加一個(gè)水晶盾牌
CrystalShield *superAvatar = [[CrystalShield alloc] init];
[superAvatar setNextAttackHandler:metalArmoredAvatar];

// ... 其他行動(dòng)

// 用劍去攻擊人物
Attack *swordAttack = [[SwordAttack alloc] init];
[superAvatar handleAttack:swordAttack];

// 然后用魔法火焰攻擊人物
Attack *magicFireAttack = [[MagicFireAttack alloc] init];
[superAvatar handleAttack:magicFireAttack];

// 現(xiàn)在用閃電進(jìn)行新的攻擊
LightningAttack *lightningAttack = [[LightningAttack alloc] init];
[superAvatar handleAttack:lightningAttack];

// ... 進(jìn)一步的行動(dòng)


// 客戶端代碼的輸出
I don't know this attack: CrystalShield
No damage from a sword attack
No damage from a magic fire
I don't know this attack: CrystalShield
I don't know this attack: MetalArmor
Oh! I'm hit with a LightningAttack

金屬盔甲為人物擋住了劍的攻擊,因?yàn)橛兴Ф芘疲Хɑ鹧婀粢矝]有傷到人物。但是第三次的閃電攻擊,盔甲和盾牌都不知道如何應(yīng)付,最后,打出來消息:Oh! I'm hit with a LightningAttack,表示因閃電攻擊而受到損傷。

15.5 何時(shí)使用責(zé)任鏈模式

  1. 有多個(gè)對(duì)象可以處理請(qǐng)求,而處理程序只有在運(yùn)行時(shí)才能確定
  2. 向一組對(duì)象發(fā)出請(qǐng)求,而不想顯示指定處理請(qǐng)求的特定處理程序

算法封裝

16. 模板方法

16.1 模板方法模式:定義一個(gè)操作中算法的骨架,而將一些步驟延遲到子類中。模板方法使子類可以重定義算法的某些特定步驟而不改變?cè)撍惴ǖ慕Y(jié)構(gòu)。

模板方法

模板方法模式是面向?qū)ο筌浖O(shè)計(jì)中一種非常簡(jiǎn)單的設(shè)計(jì)模式,其基本思想是在抽象類的一個(gè)方法中定義“標(biāo)準(zhǔn)”算法。在這個(gè)方法中調(diào)用的基本操作應(yīng)由子類重載予以實(shí)現(xiàn)。這個(gè)方法稱為模板方法。因?yàn)榉椒ǘx的算法缺少一些特有的操作。

AbstractClass 不完整地定義了一些方法與算法,留出一些操作未作定義。AbstractClass 調(diào)用的templateMethod 時(shí),方法中未定義的空白部分,由 ConcreteClass重載 primitiveOperation1(或2)來填補(bǔ)

說明: 鉤子操作給出默認(rèn)行為,子類可對(duì)其擴(kuò)展。默認(rèn)行為通常什么都不做。子類可以重載這個(gè)方法,為模板方法提供附加的操作。

16.2 在Cocoa Touch框架中使用模板方法,

利用模板方法制作三明治。包含基本步驟:準(zhǔn)備面包、把面包放在盤子上、往面包上加肉、加調(diào)味料、上餐;可以定義一個(gè)叫 make 的模板方法,它調(diào)用上述哥哥步驟來制作真正的三明治。制作真正三明治的默認(rèn)算法有些特定的操作沒有實(shí)現(xiàn),所以模板方法知識(shí)定義了制作三明治的一般方法。當(dāng)具體三明治子類重載了三明治的行為之后,客戶端僅用 make 消息就能制作真正的三明治了

代碼清單 AnySandwich.h

@interface AnySandwich : NSObject

- (void)make;

// 制作三明治的步驟
- (void)prepareBread;
- (void)putBreadOnPlate;
- (void)addMeat;
- (void)addCondiments;

// hook
- (void)extraStep;

- (void)serve;

@end

代碼清單 AnySandwich.m

- (void)make {
    [self prepareBread];
    [self putBreadOnPlate];
    [self addMeat];
    [self addCondiments];

    // hook
    [self extraStep];

    [self serve];
}

- (void)putBreadOnPlate {
    // 做任何三明治都要先把面包放在盤子上
}

// hook
- (void)extraStep {

}

- (void)serve {
    // 任何三明治做好了都要上餐
}

#pragma mark -
#pragma mark - Details will be handled by subClasses
- (void)prepareBread {
    // 要保證子類重載這個(gè)方法
}
- (void)addMeat {
    // 要保證子類重載這個(gè)方法
}
- (void)addCondiments {
    // 要保證子類重載這個(gè)方法
}

代碼清單 ReubenSandwich.h

@interface ReubenSandwich : AnySandwich

- (void)prepareBread;
- (void)addMeat;
- (void)addCondiments;

// hook
- (void)extraStep;

// 魯賓三明治的特有操作
- (void)cutRyeBread;
- (void)addCornBeef;
- (void)addSauerkraut;
- (void)addThousandIslandDressing;
- (void)addSwissCheese;

- (void)grillit;

@end

代碼清單 ReubenSandwich.m

- (void)prepareBread {
    [self cutRyeBread];
}
- (void)addMeat {
    [self addCornBeef];
}
- (void)addCondiments {
    [self addSauerkraut];
    [self addThousandIslandDressing];
    [self addSwissCheese];
}

// hook
- (void)extraStep {
    [self grillit];
}

#pragma mark -
#pragma mark - ReubenSandwich Specific methods
- (void)cutRyeBread {
    // 魯賓三明治需要兩片黑麥面包
}
- (void)addCornBeef {
    // ...... 加大量腌牛肉
}
- (void)addSauerkraut {
    // ...... 還有德國(guó)酸菜
}
- (void)addThousandIslandDressing {
    // ...... 別忘了千島醬
}
- (void)addSwissCheese {
    // ...... 還有上等瑞士奶酪
}

- (void)grillit {
    // 最后要把它烤一下
}

ReubenSandwich是AnySandwich的子類。制作魯賓三明治有其特有的步驟和配料。魯賓三明治的面包需要黑麥面包,肉需要腌牛肉,還需要德國(guó)酸菜,調(diào)味料需要千島醬和瑞士奶酪。雖然奶酪不能算調(diào)味料,但這么做可以簡(jiǎn)化制作三明治的一般步驟,因?yàn)椴皇撬腥髦味加心汤摇?/p>

代碼清單 Humburger.h

@interface Humburger : AnySandwich

- (void)prepareBread;
- (void)addMeat;
- (void)addCondiments;

// 漢堡包的特有方法
- (void)getBurgerBun;
- (void)addKetchup;
- (void)addMustard;
- (void)addBeefPatty;
- (void)addCheese;
- (void)addPickles;

@end

代碼清單 Humburger.m

- (void)prepareBread {
    [self getBurgerBun];
}
- (void)addMeat {
    [self addBeefPatty];
}
- (void)addCondiments {
    [self addKetchup];
    [self addMustard];
    [self addPickles];
}

#pragma mark -
#pragma mark - Humburger Specific Methods
- (void)getBurgerBun {
    // 漢堡包需要小圓面包
}
- (void)addKetchup {
    // 先要放番茄醬,才能加其他材料
}
- (void)addMustard {
    // 然后加點(diǎn)兒芥末醬
}
- (void)addBeefPatty {
    // 漢堡包的主料是一片牛肉餅
}
- (void)addCheese {
    // 假定漢堡包都有奶酪
}
- (void)addPickles {
    // 最后加點(diǎn)兒腌黃瓜
}

Humburger也是AnySandwich的子類,它也有自己的制作細(xì)節(jié)。漢堡包的面包需要小圓面包,肉需要牛肉餅,調(diào)味料需要番茄醬、芥末醬、腌黃瓜和奶酪。

16.2 何時(shí)實(shí)用模板方法

  1. 需要一次性實(shí)現(xiàn)算法的不變部分,并將可變的行為留給子類來實(shí)現(xiàn)
  2. 子類的共同行為應(yīng)該被提取出來放到公共類中,以避免代碼重復(fù)?,F(xiàn)有代碼的差別應(yīng)該被分離為新的操作。然后用一個(gè)調(diào)用這些新操作的模板方法來替換這些不同的代碼
  3. 需要控制子類的擴(kuò)展??梢远x一個(gè)在特定點(diǎn)調(diào)用 “鉤子”(hook)操作的模板方法,子類可以通過對(duì)鉤子操作的實(shí)現(xiàn)在這些點(diǎn)擴(kuò)展功能。
17. 策略

17.1 策略模式:定義一些列算法,把它們一個(gè)個(gè)封裝起來,并且使它們可相互替換。本模式使得算法可獨(dú)立于使用它的客戶而變化

策略模式

策略模式中的一個(gè)關(guān)鍵角色是策略類,它為所有支持的或相關(guān)的算法申明了一個(gè)共同接口。另外,還有使用策略接口來實(shí)現(xiàn)相關(guān)算法的具體策略類。場(chǎng)景(context)類的對(duì)象配置有一個(gè)具體策略對(duì)象的實(shí)例,場(chǎng)景對(duì)象使用策略接口調(diào)用由具體策略類定義的算法。

17.2 何時(shí)使用策略模式

  1. 一個(gè)類在其操作中使用多個(gè)條件語句來定義許多行為。我們可以把相關(guān)的條件分之移到它們自己的策略類中
  2. 需要算法的各種變體
  3. 需要避免把復(fù)雜的、與算法相關(guān)的數(shù)據(jù)結(jié)構(gòu)暴露給客戶端

18. 命令

18.1 命令模式:將請(qǐng)求封裝為一個(gè)對(duì)象,從而可用不同的請(qǐng)求對(duì)客戶進(jìn)行參數(shù)化,對(duì)請(qǐng)求排隊(duì)或記錄請(qǐng)求日志,以及支持可撤銷的操作。

命令模式

18.2 何時(shí)使用命令模式

  1. 想讓應(yīng)用程序支持撤銷與恢復(fù)
  2. 想用對(duì)象參數(shù)化一個(gè)動(dòng)作以執(zhí)行操作,并用不同命令對(duì)象來代替回調(diào)函數(shù)
  3. 想要在不同時(shí)刻對(duì)請(qǐng)求進(jìn)行指定、排列和執(zhí)行
  4. 想記錄修改日志,這樣在系統(tǒng)故障時(shí),這些修改可在后來重做一遍
  5. 想讓系統(tǒng)支持事務(wù),事務(wù)封裝了對(duì)數(shù)據(jù)的一些列修改。事務(wù)可以建模為命令對(duì)象
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容