iOS 設計模式

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

包含如下設計模式的概念和實際使用:

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

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


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

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

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

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

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

id <Mark> = thisMark;

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

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

Mark *thisMark;

對象創(chuàng)建

1. 原型模式:

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

原型模式

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

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

// 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)建對象
Prototype *type1 = [[ConcretePrototype1 alloc] init];
[type1 print];
Prototype *newType1 = [type1 clone];
[newType1 print];

1.3 何時實用原型模式:

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

2. 工廠方法

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

2.2 舉個例子:對象工廠與生產(chǎn)有形產(chǎn)品的真實工廠類似,例如,制鞋廠生產(chǎn)鞋,手機工廠生產(chǎn)手機。比如你讓工廠給你生產(chǎn)些產(chǎn)品。你給它們發(fā)送一個 “生產(chǎn)產(chǎn)品的消息”。制鞋廠和手機廠都按照相同的“生產(chǎn)產(chǎn)品”的協(xié)議,啟動其生產(chǎn)線。過程結(jié)束后,每個廠家都返回所生產(chǎn)的特定類型的產(chǎn)品。我們把“生產(chǎn)”這個有魔力的詞稱作為工廠方法,因為它是命令生產(chǎn)者(工廠)得到想要產(chǎn)品的方法。

工廠方法模式

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

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

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

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

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

2.4 如下情形,會考慮使用工廠方法

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

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


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

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

抽象工廠模式

4. 生成器

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

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

生成器模式

4.3 何時實用生成器模式:

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

5. 單例

5.1 單例模式:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。也是最簡單的一種模式。

單例模式

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

+ (Singleton *)sharedInstance {

    static Singleton *_instance = nil;

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

5.3 何時使用單例模式:

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

接口適配

6. 適配器

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

適配器模式

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

類適配器

  1. 只針對單一具體的 Adaptee 類,把 Adaptee 適配到 Target
  2. 易于重載 Adaptee 的行為,因為是通過直接的子類化進行的適配
  3. 只有一個 Adapter 對象,無須額外的指針間接訪問 Adaptee

對象適配器

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

6.3 使用場景

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

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

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

從圖中可看到,定義了一個叫RGBValuesProvider的塊,簽名為CGFloat ^(CGFloat *red, CGFloat *green, CGFloat *blue)。塊字面量應該符合塊簽名,并提供某些處理的實現(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)建一個顏色對象
    UIColor *color = [UIColor colorWithRed:redValue green:greenValue blue:blueValue alpha:1.0];

    // 把它賦值給當前 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實例的valueChange:事件處理器

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

7. 橋接

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

橋接模式

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

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

7.4 何時實用橋接模式

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

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

橋接_仿真器

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

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

代碼清單 ConsoleCommands.h

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

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

代碼清單 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的子類。他們重載抽象方法,并提供自己平臺的特定行為。

代碼清單 ConsoleController.h

#import "ConsoleEmulator.h"

@interface ConsoleController : NSObject

@property (nonatomic, strong) ConsoleEmulator *emulator;

- (void)setCommand:(ConsoleCommand)command;

// 其他行為和屬性

@end

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

代碼清單 ConsoleController.m

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

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

代碼清單 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)一的接口。外觀定義了上層接口,通過降低復雜度和隱藏子系統(tǒng)間的通信及依存關系,讓子系統(tǒng)更易于使用。

外觀模式

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

代碼清單 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 {
    // ...

    // 啟動計價器
    Taximeter *meter = [[Taximeter alloc] init];
    [meter start];

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

    // ......

    // 當?shù)竭_了位置 x,就停下車和計價器
    [car releaseAccelerator];
    [car pressBrakes];
    [meter stop];
}

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

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

8.3 使用場景

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

對象去耦

9. 中介者

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

中介者模式

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

9.3 使用場景

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

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

觀察者模式

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

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

10.3 何時使用觀察者模式

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

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

10.4.1 通知

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

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

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

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

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 鍵-值觀察

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

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

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

// 然后實現(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 通知和鍵-值觀察的主要卻別

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

抽象集合

11. 組合

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

組合模式

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

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

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

11.2 使用場景

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

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

組合模式

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

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

代碼清單 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é)點的個數(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é)點的個數(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;
}

// 默認屬性什么也不做
- (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;}

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

    #pragma mark -
    #pragma mark - NSCopying method
// 此方法需要實現(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_];
    // 復制 color
    [dotCopy setColor:[UIColor colorWithCGColor:[color_ CGColor]]];
    // 復制 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 {
    // 不做任何位置設定
}
- (CGPoint)location {
    // 返回第一個節(jié)點的位置
    if ([children_ count] > 0) {
        id<Mark>child = children_[0];
        return [child location];
    }
    // 否則,返回原點
    return CGPointZero;
}

- (void)addMark:(id<Mark>)mark {
    [children_ addObject:mark];
}
- (void)removeMark:(id<Mark>)mark {
    // 如果 mark 在這一層,將其移除并返回
    // 否則,讓每個自己點去找它
    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é)點的便利方法
- (id<Mark>)lastChild {
    return [children_ lastObject];
}
// 返回子節(jié)點個數(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];
    // 復制 color
    [strokeCopy setColor:[UIColor colorWithCGColor:[color_ CGColor]]];
    // 復制 size
    [strokeCopy setSize:size_];
    // 復制 children
    for (id<Mark>child in children_) {
        id<Mark>childCopy = [child copy];
        [strokeCopy addMark:childCopy];
    }
    return strokeCopy;
}

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

代碼清單,客戶端構(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];

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


12. 迭代器

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

迭代器模式

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

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

通過NSEnumerator來枚舉NSArray、NSDictionary和NSSet對象中的元素。NSEnumerator本身是個抽象類。它依靠幾個工廠方法,如:objectEnumerator或keyEnumerator,來創(chuàng)建并返回相應的具體枚舉器對象。客戶端用返回的枚舉器對象遍歷集合中的元素,

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

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

基于塊的枚舉,更加方便

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

12.2 何時實用迭代器模式

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

行為擴展

13. 訪問者

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

訪問者模式

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

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

13.4 何時實用訪問者模式

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

14. 裝飾

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

裝飾模式

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

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

14.4 何時實用裝飾模式

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

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

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

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

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

15.4 在RPG游戲中使用責任鏈模式

RPG責任鏈模式

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

代碼清單 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]]) {
        // 攻擊沒有通過這個盔甲
        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]]) {
        // 攻擊沒有通過這個盾牌
        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 {
    // 當攻擊達到這里時,我就被擊中了
    // 實際損傷的點數(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];

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

// ... 其他行動

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

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

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

// ... 進一步的行動


// 客戶端代碼的輸出
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

金屬盔甲為人物擋住了劍的攻擊,因為有水晶盾牌,魔法火焰攻擊也沒有傷到人物。但是第三次的閃電攻擊,盔甲和盾牌都不知道如何應付,最后,打出來消息:Oh! I'm hit with a LightningAttack,表示因閃電攻擊而受到損傷。

15.5 何時使用責任鏈模式

  1. 有多個對象可以處理請求,而處理程序只有在運行時才能確定
  2. 向一組對象發(fā)出請求,而不想顯示指定處理請求的特定處理程序

算法封裝

16. 模板方法

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

模板方法

模板方法模式是面向?qū)ο筌浖O計中一種非常簡單的設計模式,其基本思想是在抽象類的一個方法中定義“標準”算法。在這個方法中調(diào)用的基本操作應由子類重載予以實現(xiàn)。這個方法稱為模板方法。因為方法定義的算法缺少一些特有的操作。

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

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

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

利用模板方法制作三明治。包含基本步驟:準備面包、把面包放在盤子上、往面包上加肉、加調(diào)味料、上餐;可以定義一個叫 make 的模板方法,它調(diào)用上述哥哥步驟來制作真正的三明治。制作真正三明治的默認算法有些特定的操作沒有實現(xiàn),所以模板方法知識定義了制作三明治的一般方法。當具體三明治子類重載了三明治的行為之后,客戶端僅用 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 {
    // 要保證子類重載這個方法
}
- (void)addMeat {
    // 要保證子類重載這個方法
}
- (void)addCondiments {
    // 要保證子類重載這個方法
}

代碼清單 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 {
    // ...... 還有德國酸菜
}
- (void)addThousandIslandDressing {
    // ...... 別忘了千島醬
}
- (void)addSwissCheese {
    // ...... 還有上等瑞士奶酪
}

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

ReubenSandwich是AnySandwich的子類。制作魯賓三明治有其特有的步驟和配料。魯賓三明治的面包需要黑麥面包,肉需要腌牛肉,還需要德國酸菜,調(diào)味料需要千島醬和瑞士奶酪。雖然奶酪不能算調(diào)味料,但這么做可以簡化制作三明治的一般步驟,因為不是所有三明治都有奶酪。

代碼清單 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 {
    // 然后加點兒芥末醬
}
- (void)addBeefPatty {
    // 漢堡包的主料是一片牛肉餅
}
- (void)addCheese {
    // 假定漢堡包都有奶酪
}
- (void)addPickles {
    // 最后加點兒腌黃瓜
}

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

16.2 何時實用模板方法

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

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

策略模式

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

17.2 何時使用策略模式

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

18. 命令

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

命令模式

18.2 何時使用命令模式

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

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

  • 設計模式匯總 一、基礎知識 1. 設計模式概述 定義:設計模式(Design Pattern)是一套被反復使用、多...
    MinoyJet閱讀 4,094評論 1 15
  • 設計模式基本原則 開放-封閉原則(OCP),是說軟件實體(類、模塊、函數(shù)等等)應該可以拓展,但是不可修改。開-閉原...
    西山薄涼閱讀 4,082評論 3 14
  • 工廠模式類似于現(xiàn)實生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情,實現(xiàn)同樣的效果;這時候需要使用工廠模式。簡單...
    舟漁行舟閱讀 8,131評論 2 17
  • 文章部分內(nèi)容轉(zhuǎn)載自:http://blog.csdn.net/zhangerqing 一、設計模式的分類 總體來說...
    j_cong閱讀 2,142評論 0 20
  • 更新3 另一種更為簡單的方式(缺點是:局域網(wǎng)其他機器無法訪問該虛擬機) 宿主需要有samba/配置好samba的用...
    Quasars閱讀 15,948評論 0 3

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