iOS-設計模式簡析

參考:《Objective-C編程之道》html

前言

這邊文章是在學習了《Objective-C編程之道》後的作的一些筆記,在其中參考了其餘相關書籍或者其餘前輩的例子以及一些文章內容,有引用到的地方我已經儘可能添加原文連接或者引用了,若是發現本文侵權可聯繫刪除.node

此篇文章主要是對各個模式對介紹,此處引用對例子是我認爲比較合適比較容易理解對例子,不過也會有一些片面,沒法徹底解決各個模式.這篇文章總的來講適合用來入門設計模式.ios

此篇文章可能會有不少不足之處,歡迎你們指出批評,也歡迎你們一塊兒來學習探討.c++

對象建立

原型

1.概述

使用原型實例指定建立對象的種類,並經過複製這個原型建立新的對象.git

2.使用場景

  • 須要建立的對象應獨立於其類型與建立方式.
  • 要實例化的類是在運行時決定的.
  • 不想要與產品層次相對應的工廠層次.
  • 不一樣類的實例間的差別僅是狀態的若干組合所以複製相應數量的原型比手工實例化更加方便.
  • 類不容易建立,好比每一個組件可把其餘組件做爲子節點的組合對象.複製已有的組合對象並對副本進行修改會更加容易.

3.結構UML圖

4.實例

數據模型間的複製github

Student *s1 = [[Student alloc] init];
    s1.age = 18;
    s1.name = @"s1";
    s1.address = @"adress";
    s1.size = CGSizeMake(100, 100);
    s1.teacher = p1;
    s1.friends = @[p1, p2];
    s1.girlfriends = [[NSMutableArray alloc] initWithArray:s1.friends];
    s1.other = @[s1.friends, s1.girlfriends];
    
    Student *s2 = [s1 copy];
複製代碼

這裏經過copy可以拷貝獲得一個數據一致的Student.算法

這裏須要特別注意的是淺複製深複製之間的區別.macos

工廠方法

1.概述

  • 工廠方法的最初定義是專一於讓子類決定建立什麼對象.
  • 工廠方法也稱爲虛構造器.它適用於這種狀況:一個類沒法預期須要生成哪一個類的對象,想讓子類來指定所生成的對象.
  • 定義建立對象的接口,讓子類決定實例化哪個類.工廠方法使得一個類的實現化延遲到其子類.

2.使用場景

  • 編譯時沒法準確預期要建立的對象的類
  • 類想讓其子類決定在運行時要建立什麼
  • 類有若干輔助類爲其子類,而你想將返回哪個子類這一信息局部化

3.結構UML圖

4.實例: 建立工廠類,開放接口,生成對應的類

+ (UITableViewCell *)cellWithText:(NSString *)text {
    LabelTableViewCell *cell = [[LabelTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
    cell.textLabel.text = text;
    return cell;
}

+ (UITableViewCell *)cellWithImage:(UIImage *)img {
    ImageTableViewCell *cell = [[ImageTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
    cell.imageView.image = img;
    return cell;
}

+ (UITableViewCell *)cellWithSelect:(BOOL)isSelect {
    SwitchTableViewCell *cell = [[SwitchTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
    [cell setSwitchSelect:isSelect];
    return cell;
}
複製代碼

iOS中能夠參考系統類NSNumber;編程

- (NSNumber *)initWithChar:(char)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithUnsignedChar:(unsigned char)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithShort:(short)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithUnsignedShort:(unsigned short)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithInt:(int)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithUnsignedInt:(unsigned int)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithLong:(long)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithUnsignedLong:(unsigned long)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithLongLong:(long long)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithUnsignedLongLong:(unsigned long long)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithFloat:(float)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithDouble:(double)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithBool:(BOOL)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithInteger:(NSInteger)value API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithUnsignedInteger:(NSUInteger)value API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) NS_DESIGNATED_INITIALIZER;

複製代碼

抽象工廠

1.概述

提供一個建立一系列相關或相互依賴對象的接口,而無需指定他們具體的類.canvas

2.結構UML圖

3.代碼實例:建立不一樣類型Cell

抽象工廠AbstractFactory

@interface AbstractFactory : NSObject

+ (UITableViewCell *)cellWithText:(NSString *)text;
+ (UITableViewCell *)cellWithImage:(UIImage *)img;

@end
複製代碼

繼承AbstractFactory的普通工廠CellFactory

@implementation CellFactory

+ (UITableViewCell *)cellWithText:(NSString *)text {
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
    cell.textLabel.text = text;
    return cell;
}

+ (UITableViewCell *)cellWithImage:(UIImage *)img {
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
    cell.imageView.image = img;
    return cell;
}

@end
複製代碼

繼承AbstractFactory的可點擊Cell工廠CellFactory

@implementation ClickFactory

+ (UITableViewCell *)cellWithText:(NSString *)text {
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
    cell.textLabel.text = text;
    cell.textLabel.userInteractionEnabled = YES;
    return cell;
}

+ (UITableViewCell *)cellWithImage:(UIImage *)img {
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
    cell.imageView.image = img;
    cell.imageView.userInteractionEnabled = YES;
    return cell;
}

@end

複製代碼

控制器實現

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell;
    
    switch (indexPath.row % 2) {
        case 0:
            if (indexPath.row > 2) {
                cell = [ClickFactory cellWithText:[NSString stringWithFormat:@"%ld", indexPath.row]];
            } else {
                cell = [CellFactory cellWithText:[NSString stringWithFormat:@"%ld", indexPath.row]];
            }
            break;
        case 1:
            if (indexPath.row > 2) {
                cell = [ClickFactory cellWithImage:[UIImage imageNamed:@"12.png"]];
            } else {
                cell = [CellFactory cellWithImage:[UIImage imageNamed:@"12.png"]];
            }
            break;
        case 2:
            break;
        default:
            break;
    }
    return cell;
}
複製代碼

4.工廠模式抽象工廠模式對比

抽象工廠工廠模式在許多方面都很是相似.不少人經常搞不清楚應該在何時用哪個.兩個模式都用於相同的目的:建立對象而不讓客戶端知曉返回了什麼確切的具體對象.

抽象工廠 工廠方法
經過對象組合建立抽象產品 經過類繼承建立抽象產品
建立多系列產品 建立一種產品
必須修改父類的接口才能支持新的產品 子類化建立者並重載工廠方法以建立新產品

5.總結

抽象工廠模式是一種極爲常見的設計模式.它是最基本的,由於它能夠涉及許多類型的對象建立.一系列相關類的好的模式,應該做爲一種抽象,不爲客戶端所見.抽象工廠能夠順暢地提供這種抽象,而不暴露建立過程當中任何沒必要要的細節或所建立對象的確切類型.

生成器

1.概述

將一個複雜對象的構建與它的表現分離,使得一樣的構建過程能夠建立不一樣的表現.

2.使用場景

  • 須要建立涉及各類部件的複雜對象.建立對象的算法應該獨立於部件的裝配方式.常見例子是構建組合對象.

  • 構建過程須要以不一樣的方式構建對象.

3.結構UML圖以及交互時序圖

4.代碼實例:製做三明治

定義指導者SandwichDirector

@interface SandwichDirector : NSObject

// 具體的生成類
@property (nonatomic, strong) id<SandwichBuilder> concreteBuilder;

// 生成動做
- (void)construct;

@end

規定製做三明治流程

@implementation SandwichDirector

- (void)construct {
    [_concreteBuilder prepareBread];
    [_concreteBuilder addMeat];
}

@end
複製代碼
@protocol SandwichBuilder <NSObject>

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

@end
複製代碼

基礎三明治類Sandwich

@interface Sandwich : NSObject

@property (nonatomic, copy) NSString *breadType;
@property (nonatomic, copy) NSString *meatType;

@end

複製代碼

製做魯賓三明治類SandwichConcreteBuilder

@interface SandwichConcreteBuilder : NSObject<SandwichBuilder>

// 獲取生成的三明治
- (Sandwich *)getSandwich;

@end

@interface SandwichConcreteBuilder ()

@property (nonatomic, strong) Sandwich *reubenSandwich;


@end

@implementation SandwichConcreteBuilder

- (instancetype)init {
    if (self = [super init]) {
        _reubenSandwich = [[Sandwich alloc] init];
    }
    return self;
}

- (void)prepareBread {
    _reubenSandwich.breadType = @"黑麥麪包";
}

- (void)addMeat {
    _reubenSandwich.meatType = @"醃牛肉";
}

- (id)getSandwich {
    return _reubenSandwich;
}

複製代碼

獲取魯賓三明治

SandwichDirector *director = [[SandwichDirector alloc] init];
    SandwichConcreteBuilder *builder = [[SandwichConcreteBuilder alloc] init];
    [director setConcreteBuilder:builder];
    [director construct];
    
    Sandwich *sandwich = [builder getSandwich];
    NSLog(@"麪包:%@\n肉:%@", sandwich.breadType, sandwich.meatType);
複製代碼

5.總結

生成器模式能幫助構建涉及部件與表現的各類組合的對象.沒有這一模式,知道構建對象所需細節的Director可能最終會變成一個龐大的"神"類,帶有無數用於構建同一個類的各類表現的內嵌算法.

單例

1.概述

保證一個類僅有一個實例,並提供一個訪問它的全局訪問點.

2. 使用場景

  • 類只能有一個實例,並且必須從一個爲人熟知的訪問點對其進行訪問,好比工廠方法.
  • 這個惟一的實例只能經過子類化進行擴展,並且擴展的對象不會破壞客戶端代碼.
  • 須要提供一個統一的接口,用來遍歷各類類型的組合對象.

3.結構UML圖

4.代碼實例:簡單單例實現

@interface Singleton : NSObject

/// 統一的接口
+ (instancetype)defaultSingleton;

@end

static Singleton *_singleton;

@implementation Singleton

+ (instancetype)defaultSingleton {
    return [[self alloc] init];
}

- (instancetype)init {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _singleton = [super init];
    });
    return _singleton;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    if (!_singleton) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _singleton = [super allocWithZone:zone];
        });
    }
    return _singleton;
}

- (id)copy {
    return _singleton;
}

- (id)mutableCopy {
    return _singleton;
}

@end

複製代碼

由於單例只會存在一個對象,因此sigleton0sigleton1等價的.

Singleton *sigleton0 = [[Singleton alloc] init];
    NSLog(@"%@", sigleton0);
    
    Singleton *sigleton1 = [Singleton defaultSingleton];
    NSLog(@"%@", sigleton1);
    
    NSLog(@"%@", [sigleton0 copy]);
    NSLog(@"%@", [sigleton1 mutableCopy]);
複製代碼

5.總結

幾乎在任何類型的應用程序中,單例模式都極爲常見,並不僅限於iOS應用程序開發.只要應用程序須要用集中式的類來協調其服務,這個類就應生成單一的實例,而不是多個實例.

接口適配

適配器

1.概述

將一個類的接口轉換成客戶但願的另一個接口.適配器模式使得本來因爲接口不兼容而不能一塊兒工做的那些類能夠一塊兒工做.

2.使用場景

  • 已有類的接口與需求不匹配
  • 想要一個可複用的類,該類可以同可能帶有不兼容接口的其餘類寫做.
  • 須要適配一個類的幾個不一樣子類,但是讓每個子類去子類化一個類適配器又不現實.那麼可使用對象適配器(也叫委託)來適配其父類的接口.

3.結構UML圖

類適配器類圖

對象適配器類圖

4.代碼實例簡單適配器實現

參考: Objective C--適配器模式

初始請求目標執行類

@interface Target : NSObject

- (void)request;

@end

@implementation Target

- (void)request {
    NSLog(@"請求");
}

@end
複製代碼

新的執行類

@interface Adaptee : NSObject

- (void)specialRequest;

@end

@implementation Adaptee

- (void)specialRequest {
    NSLog(@"特殊請求");
}

@end
複製代碼

適配器類實現

@interface Adapter : Target

@property (nonatomic, strong) Adaptee *adaptee;

@end

@implementation Adapter

- (instancetype)init {
    if (self = [super init]) {
        self.adaptee = [[Adaptee alloc] init];
    }
    return self;
}

- (void)request {
    [self.adaptee specialRequest];
}

@end

複製代碼

控制器調用

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Target *target = [[Adapter alloc] init];
    [target request];
}

@end
複製代碼

5.總結

適配器模式能夠用做爲目標接口的協議來實現,協議定義客戶端要求的,也是適配器能夠保證的一些標準行爲.協議Object-C中語言級別的功能,經過它能夠定義用做適配器模式的實例的接口.

橋接

1.概述

將抽象部分與它的實現部分分離,使它們均可以獨立地變化.

2.使用場景

  • 不想在抽象與其實現之間造成固定的綁定關係.
  • 抽象及其實現都應能夠經過子類化獨自進行擴展.
  • 對抽象的實現進行修改不該影響客戶端代碼.
  • 若是每一個實現須要額外的子類以細化抽象,則說明有必要把它們分紅兩個部分.
  • 想在帶有不一樣抽象接口的多個對象之間共享一個實現.

3.結構UML圖

4.代碼實例 經過手機SMS發送QQ消息或者普通消息.

參考:設計模式系列 11-- 橋接模式

接口協議MessageImplement

@protocol MessageImplement <NSObject>

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

@end
複製代碼

抽象信息類AbstractMessage

/// 抽象信息類
@interface AbstractMessage : NSObject

@property (nonatomic, strong) id<MessageImplement> messageIm;

- (void)send:(NSMutableString *)message;
- (instancetype)initWithImplement:(id<MessageImplement>)implement;

@end

@implementation AbstractMessage

- (instancetype)initWithImplement:(id<MessageImplement>)implement {
    if (self = [super init]) {
        self.messageIm = implement;
    }
    return self;
}

- (void)send:(NSMutableString *)message {
    
}

@end
複製代碼

普通訊息類CommonMessage

/// 普通訊息類
@interface CommonMessage : AbstractMessage

@end

@implementation CommonMessage

- (void)send:(NSMutableString *)message {
    [message insertString:@"【普通消息:" atIndex:0];
    [message appendString:@"】"];
    [self.messageIm sendMessage:message];
}

@end
複製代碼

QQ信息類QQMessage

/// QQ信息類
@interface QQMessage : AbstractMessage

@end

@implementation QQMessage

- (void)send:(NSMutableString *)message {
    [message insertString:@"【QQ消息:" atIndex:0];
    [message appendString:@"】"];
    [self.messageIm sendMessage:message];
}

@end
複製代碼

系統發送類MessageSMS

/// 系統發送類
@interface MessageSMS : NSObject<MessageImplement>

@end

@implementation MessageSMS

- (void)sendMessage:(NSString *)message {
    NSLog(@"使用系統發送消息:%@", message);
}

@end
複製代碼

手機發送類MessageTEL

/// 手機發送類
@interface MessageTEL : NSObject<MessageImplement>

@end

@implementation MessageTEL

- (void)sendMessage:(NSString *)message {
    NSLog(@"使用手機發送消息:%@", message);
}

@end
複製代碼

控制器實現

- (void)viewDidLoad {
    [super viewDidLoad];
    
    MessageSMS *sms = [[MessageSMS alloc] init];
    MessageTEL *tel = [[MessageTEL alloc] init];
    
    QQMessage *messageSMS = [[QQMessage alloc] initWithImplement:sms];
    [messageSMS send:[[NSMutableString alloc] initWithString:@"SMS->QQ"]];
    NSLog(@"\n");
    
    QQMessage *messageTEL = [[QQMessage alloc] initWithImplement:tel];
    [messageTEL send:[[NSMutableString alloc] initWithString:@"TEL->QQ"]];
    NSLog(@"\n");

    CommonMessage *commonSMS = [[CommonMessage alloc] initWithImplement:sms];
    [commonSMS send:[[NSMutableString alloc] initWithString:@"SMS->Common"]];
    NSLog(@"\n");

    CommonMessage *commonTEL = [[CommonMessage alloc] initWithImplement:tel];
    [commonTEL send:[[NSMutableString alloc] initWithString:@"TEL->Common"]];
}

@end
複製代碼

結果展現

2019-04-10 22:25:22.611392+0800 BridgePattern[46027:874792] 使用系統發送消息:【QQ消息:SMS->QQ】
2019-04-10 22:25:22.611661+0800 BridgePattern[46027:874792] 
2019-04-10 22:25:25.998905+0800 BridgePattern[46027:874792] 使用手機發送消息:【QQ消息:TEL->QQ】
2019-04-10 22:25:25.999066+0800 BridgePattern[46027:874792] 
2019-04-10 22:25:25.999229+0800 BridgePattern[46027:874792] 使用系統發送消息:【普通消息:SMS->Common】
2019-04-10 22:25:25.999343+0800 BridgePattern[46027:874792] 
2019-04-10 22:25:25.999468+0800 BridgePattern[46027:874792] 使用手機發送消息:【普通消息:TEL->Common】
複製代碼

5.總結

橋接模式是把一個接口適配到不一樣接口的一種方式.

外觀

1.概述

爲系統中的一組接口提供一個統一的接口.外觀定義一個高層接口,讓子系統更易於使用.

2.使用場景

  • 子系統正逐漸變得複雜.應用模式的過程當中演化出許多類.可使用外觀爲這些子系統提供一個簡單的接口.

  • 可使用外觀對子系統進行分層.每一個子系統級別有一個外觀做爲入口點.讓它們經過其外觀進行通訊,能夠簡化它們的依賴關係.

3.結構UML圖iOS 設計模式-結構模式-外觀模式

4.代碼實例-- 出租車爲駕駛出租車的一組複雜接口提供一個簡化的接口

定義汽車類Car

@interface Car : NSObject

- (void)releaseBreaks;
- (void)changeGears;
- (void)pressAccelerator;
- (void)pressBreaks;
- (void)releaseAccelerator;

@end


@implementation Car

- (void)releaseBreaks {
    NSLog(@"鬆開剎車");
}

- (void)changeGears {
    NSLog(@"換擋");
}

- (void)pressAccelerator {
    NSLog(@"踩油門");
}

- (void)pressBreaks {
    NSLog(@"鬆油門");
}

- (void)releaseAccelerator {
    NSLog(@"剎車");
}

@end

複製代碼

計價器類Taximeter實現

@interface Taximeter : NSObject

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

@end

@implementation Taximeter

- (void)start {
    NSLog(@"啓動計價器");
}

- (void)stop {
    NSLog(@"停下計價器");
}
複製代碼

外觀類CabDriverdriveToLocation接口整合Taximeter Car子系統爲外部提供接口

@interface CabDriver : NSObject

- (void)driveToLocation:(CGPoint)x;

@end


@implementation CabDriver

- (void)driveToLocation:(CGPoint)x {
    // 啓動計價器
    Taximeter *meter = [[Taximeter alloc] init];
    [meter start];
    
    // 操做車輛,直到抵達位置
    Car *car = [[Car alloc] init];
    [car releaseBreaks];
    [car changeGears];
    [car pressAccelerator];
    
    // 當到達了位置x,就停下車和計價器
    [car releaseAccelerator];
    [car pressBreaks];
    [meter stop];
}

@end
複製代碼

5.總結

外觀有助於提供一種更爲簡潔的方式來使用子系統中的這些類.處理這些子系統類的默認行爲,可能只是定義在外觀的一個簡便方法,而沒必要直接去使用這些類時即可以使用外觀設計模式了.

對象去耦

中介者

1.概述

用一個對象來封裝一系列對象的交互方式.中介者使對象不須要顯式地相互引用,從而使其耦合鬆散,並且能夠獨立地改變它們之間的交互.

2.使用場景

  • 對象間的交互定義明確而很是複雜,致使一組對象彼此相互依賴並且難以理解.
  • 由於對象引用了許多其餘對象並與其通信,致使對象難以複用.
  • 想要定製一個分佈在多個類中的邏輯或行爲,又不想生成太多子類.

3.結構UML圖

4.代碼實例--實例間的信息通知

原文: Objective-C設計模式——中介者Mediator

中介者模式很好的詮釋了迪米特法則,任意兩個不相關的對象之間若是須要關聯,那麼須要經過第三個類來進行。中介者就是把一組對象進行封裝,屏蔽了類之間的交互細節,使不一樣的類直接不須要持有對方引用也能夠進行訪問。

中介者Mediator會持有同事類(就是須要處理交互邏輯的對象)Colleague的引用,同時每一個colleague也會持有Mediator一份引用。這樣colleague若是有任何和別的類交互的請求就會發給Mediator,對改組對象進行了解耦合。其實咱們平時常常寫的視圖控制器自己就是一個中介者,它來控制着不一樣對象之間的交互行爲。

中介者Mediator

@interface Mediator : NSObject

@property (nonatomic, weak) ColleagueA *collA;
@property (nonatomic, weak) ColleagueB *collB;

- (void)notify:(id)obj;

@end

@implementation Mediator

- (void)notify:(id)obj {
    if (obj == self.collA) {
        [self.collB notified:@"B Notified"];
    } else {
        [self.collA notified:@"A Notified"];
    }
}

@end
複製代碼

同事類基類Colleague

@interface Colleague : NSObject

@property (nonatomic, weak) Mediator *mediator;

- (instancetype)initWithMediator:(Mediator *)mediator;

- (void)notifyAnother;
- (void)notified:(NSString *)message;

@end

@implementation Colleague

- (instancetype)initWithMediator:(Mediator *)mediator {
    if (self = [super init]) {
        self.mediator = mediator;
    }
    return self;
}

- (void)notifyAnother {
    [self.mediator notify:self];
}

- (void)notified:(NSString *)message {
    NSLog(@"%@", message);
}

@end
複製代碼

同事類AColleagueA

@interface ColleagueA : Colleague

@end

@implementation ColleagueA

@end
複製代碼

設置同事類BColleagueB與同事類A一致

控制器實現

Mediator *mediator = [[Mediator alloc] init];
    ColleagueA *caA = [[ColleagueA alloc] initWithMediator:mediator];
    ColleagueB *caB = [[ColleagueB alloc] initWithMediator:mediator];
    
    mediator.collA = caA;
    mediator.collB = caB;
    
    [caA notifyAnother];
    [caB notifyAnother];
複製代碼

結果展現

2019-04-18 23:27:55.290764+0800 MediatorPattern[86780:1876003] B Notified
2019-04-18 23:27:55.290933+0800 MediatorPattern[86780:1876003] A Notified
複製代碼

5.總結

雖然對於處理應用程序的行爲分散於不一樣對象而且對象相互依存的狀況,中介者模式很是有用,可是應當注意避免讓中介者過於龐大而難以維護.若是已經這樣了,能夠考慮使用另外一種設計模式把它分解.要創造性地混用和組合各類設計模式解決同一個問題.

擴展: 組件化和模塊化間用於各個模塊的跳轉交互所用的就是中介者模式.

觀察者

1.概述

  • 觀察者模式也叫發佈-訂閱模式.
  • 定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都獲得通知並被自動更新.

2.使用場景

  • 有兩種抽象類型相互依賴.將它們封裝在各自的對象中,就能夠對它們單獨進行改變和複用.
  • 對一個對象的改變須要同時改變其餘對象,而不知道具體有多少對象有待改變.
  • 一個對象必須通知其餘對象,而它又不須要知道其餘對象是什麼.

3.結構UML圖

4.代碼實例--Cocoa Touch框架中使用的觀察者模式--通知鍵-值觀察

定義模型類Person

UIKIT_EXTERN NSNotificationName const PersonNameWillChangeNotification;

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

@end

NSNotificationName const PersonNameWillChangeNotification = @"PersonNameWillChangeNotification";

@implementation Person

- (void)setName:(NSString *)name {
    _name = name;
    
    // 發送通知
    [[NSNotificationCenter defaultCenter] postNotificationName:PersonNameWillChangeNotification object:_name];
}

@end
複製代碼

控制器實現

對象賦初值

_xiaoming = [[Person alloc] init];
    _xiaoming.name = @"xiaoming";
    _xiaoming.age = 11;
複製代碼

經過通知監聽名字更替

// 通知更換了名字
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changePersonName:) name:PersonNameWillChangeNotification object:nil];
    
    _xiaoming.name = @"wangxiaoming";
    
    - (void)changePersonName:(NSNotification *)noti {
    NSLog(@"newName = %@", noti.object);
}

//2019-03-29 22:39:23.243012+0800 ObserverPattern[9755:1317800] newName = wangxiaoming
複製代碼

經過鍵-值觀察監聽年齡變化

[self.xiaoming addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];

    _xiaoming.age = 28;

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"newAge = %ld", [change[NSKeyValueChangeNewKey] integerValue]);
    
    //2019-03-29 22:39:23.243229+0800 ObserverPattern[9755:1317800] newAge = 28

}
複製代碼

具體實現

- (void)viewDidLoad {
    [super viewDidLoad];

    _xiaoming = [[Person alloc] init];
    _xiaoming.name = @"xiaoming";
    _xiaoming.age = 11;
    
    // 通知更換了名字
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changePersonName:) name:PersonNameWillChangeNotification object:nil];

    // 監聽xiaoming年齡變化
    [self.xiaoming addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
    
    _xiaoming.name = @"wangxiaoming";
    _xiaoming.age = 28;
    
    // 移除相關監聽
    [[NSNotificationCenter defaultCenter] removeObserver:self name:PersonNameWillChangeNotification object:nil];
    [self.xiaoming removeObserver:self forKeyPath:@"age"];
}

- (void)changePersonName:(NSNotification *)noti {
    NSLog(@"newName = %@", noti.object);
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"newAge = %ld", [change[NSKeyValueChangeNewKey] integerValue]);
}
複製代碼

5.總結

通知和KVOiOS中經常使用的數據交互傳輸的兩種手段.特別是在需求須要雙向綁定的狀況下KVO是很是適合使用的.開發中可使用KVO來實現類MVVM這樣的框架.

抽象集合

組合

1.概述

將對象組合成樹形結構以表示"部分-總體"的層次結構.組合使得用戶對單個對象和組合對象的使用具備一致性.

2.使用場景

  • 想得到對象抽象的樹形表示(部分-總體層次結構).
  • 想讓客戶端統一處理組合結構中的全部對象.

3.結構UML圖

4.代碼實例

原文: 組合模式(Composite)

定義基礎組件類Components

@interface Components : NSObject

@property (nonatomic, copy) NSString *name;

- (instancetype)initWithName:(NSString *)name;
- (void)addComponent:(Components *)component;
- (void)removeComponent:(Components *)component;
- (void)display:(NSInteger)depth;

@end

@implementation Components

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

- (void)addComponent:(Components *)component {
    return;
}

- (void)removeComponent:(Components *)component {
    return;
}

- (void)display:(NSInteger)depth {
    return;
}

@end

複製代碼

定義葉子的子類Leaf

@interface Leaf : Components

@end

@implementation Leaf

- (void)addComponent:(Components *)component {
    NSLog(@"can not add");
}

- (void)removeComponent:(Components *)component {
    NSLog(@"can not remove");
}

- (void)display:(NSInteger)depth {
    NSLog(@"[level:%ld_%@]",depth, self.name);
}

@end
複製代碼

定義節點類Composite,節點可包含新節點或新葉子

@interface Composite : Components

@property (nonatomic, strong) NSMutableArray *childArr;

@end

@implementation Composite

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

- (void)addComponent:(Components *)component {
    [self.childArr addObject:component];
}

- (void)removeComponent:(Components *)component {
    [self.childArr removeObject:component];
}

- (void)display:(NSInteger)depth {
    NSLog(@"[level:%ld_%@]",depth, self.name);
    for (Components *component in self.childArr) {
        [component display:depth + 1];
    }
}

@end

複製代碼

控制器實現

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Composite *root = [[Composite alloc] initWithName:@"root"];
    [root addComponent:[[Leaf alloc] initWithName:@"leaf-A"]];
    [root addComponent:[[Leaf alloc] initWithName:@"leaf-B"]];
   
    Composite *comp0 = [[Composite alloc] initWithName:@"comp0"];
    [comp0 addComponent:[[Leaf alloc] initWithName:@"comp0-A"]];
    [comp0 addComponent:[[Leaf alloc] initWithName:@"comp0-B"]];
    [root addComponent:comp0];
    
    Composite *comp1 = [[Composite alloc] initWithName:@"comp1"];
    [comp1 addComponent:[[Leaf alloc] initWithName:@"comp1-A"]];
    [comp1 addComponent:[[Leaf alloc] initWithName:@"comp1-B"]];
    [root addComponent:comp1];
    
    Composite *comp00 = [[Composite alloc] initWithName:@"comp00"];
    [comp00 addComponent:[[Leaf alloc] initWithName:@"comp00-A"]];
    [comp00 addComponent:[[Leaf alloc] initWithName:@"comp00-B"]];
    [comp0 addComponent:comp00];
    
    Leaf *leafC = [[Leaf alloc] initWithName:@"leaf-C"];
    [root addComponent:leafC];
    [root addComponent:[[Leaf alloc] initWithName:@"leaf-D"]];
    
    [root display:0];
    
    NSLog(@"-------- 刪除leaf-C --------");

    // 刪除leaf-C
    [root removeComponent:leafC];
    [root display:0];
    
    NSLog(@"-------- 刪除comp0 --------");
    // 刪除comp0
    [root removeComponent:comp0];
    [root display:0];
}


@end

複製代碼

結果展現

[level:0_root]
[level:1_leaf-A]
[level:1_leaf-B]
[level:1_comp0]
[level:2_comp0-A]
[level:2_comp0-B]
[level:2_comp00]
[level:3_comp00-A]
[level:3_comp00-B]
[level:1_comp1]
[level:2_comp1-A]
[level:2_comp1-B]
[level:1_leaf-C]
[level:1_leaf-D]
-------- 刪除leaf-C --------
[level:0_root]
[level:1_leaf-A]
[level:1_leaf-B]
[level:1_comp0]
[level:2_comp0-A]
[level:2_comp0-B]
[level:2_comp00]
[level:3_comp00-A]
[level:3_comp00-B]
[level:1_comp1]
[level:2_comp1-A]
[level:2_comp1-B]
[level:1_leaf-D]
-------- 刪除comp0 --------
[level:0_root]
[level:1_leaf-A]
[level:1_leaf-B]
[level:1_comp1]
[level:2_comp1-A]
[level:2_comp1-B]
[level:1_leaf-D]
複製代碼

5.總結

Cocoa Touch框架中,UIView被組織成一個組合結構.每一個UIView的實例能夠包含UIView的其餘實例,造成統一的樹形結構.讓客戶端對單個UIView對象和UIView的組合統一對待.

組合模式的主要意圖是讓樹形結構中的每一個節點具備相同的抽象接口.這樣整個結構可做爲一個統一的抽象結構使用,而不暴露其內部展現.對每一個節點(葉節點或組合體)的任何操做,能夠經過協議或抽象基類中定義的相同接口來進行.

迭代器

1.概述

提供一種方法順序訪問一個聚合對象中各個元素,而又不須要暴露該對象的內部表示.

2.使用場景

  • 須要訪問組合對象的內容,而又不暴露其內部表示.
  • 須要經過多種方式遍歷組合對象.
  • 須要提供一個統一的接口,用來遍歷各類類型的組合對象.

3.結構UML圖

ListListIterator之間關係的類圖

抽象列表與迭代器之間關係的類圖

4.代碼實例

參考:iOS設計模式--迭代器

4.1 系統迭代器
// 系統迭代器
    NSArray *arr = @[@"A", @"B", @"C", @"D"];
    NSEnumerator *enumerator = [arr objectEnumerator];
    NSString *obj = nil;
    while ((obj = enumerator.nextObject)) {
        NSLog(@"%@", obj);
    }
複製代碼
4.2 自定義迭代器

定義節點類Node

@interface Node : NSObject

/// 下一節點
@property (nonatomic, strong) Node *nextNode;
@property (nonatomic, strong) NSString *nodeName;

+ (instancetype)nodeWithName:(NSString *)name;

@end

@implementation Node

+ (instancetype)nodeWithName:(NSString *)name {
    Node *node = [[Node alloc] init];
    node.nodeName = name;
    return node;
}


@end

複製代碼

定義鏈表類LinkedList

@interface LinkedList : NSObject

@property (nonatomic, strong, readonly) Node *firstNode;
@property (nonatomic, assign, readonly) NSInteger count;

- (void)addItem:(NSString *)item;

@end

@interface LinkedList ()

@property (nonatomic, strong) Node *firstNode;
@property (nonatomic, assign) NSInteger count;

@end

@implementation LinkedList

- (void)addItem:(NSString *)item {
    if (self.firstNode == nil) {
        self.firstNode = [Node nodeWithName:item];
        self.count++;
    } else {
        [self addItem:item node:self.firstNode];
    }
}

- (void)addItem:(NSString *)item node:(Node *)node {
    if (!node.nextNode) {
        node.nextNode = [Node nodeWithName:item];
        self.count++;
    } else {
        [self addItem:item node:node.nextNode];
    }
}

@end
複製代碼

定義協議IteratorProtocol

@protocol IteratorProtocol <NSObject>

// 下一個元素
- (id)nextObject;

@end
複製代碼

定義迭代器類LinkedListIterator

@interface LinkedListIterator : NSObject<IteratorProtocol>

+ (instancetype)linkedListIteratorWithLinkedList:(LinkedList *)linkedList;

@end

@interface LinkedListIterator ()

@property (nonatomic, strong) LinkedList *linkedList;
@property (nonatomic, strong) Node *currentNode;

@end

@implementation LinkedListIterator

+ (instancetype)linkedListIteratorWithLinkedList:(LinkedList *)linkedList {
    LinkedListIterator *iterator = [[LinkedListIterator alloc] init];
    iterator.linkedList = linkedList;
    return iterator;
}

- (id)nextObject {
    if (!self.currentNode) {
        self.currentNode = self.linkedList.firstNode;
    } else {
        self.currentNode = self.currentNode.nextNode;
    }
    return self.currentNode;
}

@end

複製代碼

控制器實現

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 系統迭代器
    NSArray *arr = @[@"A", @"B", @"C", @"D"];
    NSEnumerator *enumerator = [arr objectEnumerator];
    NSString *obj = nil;
    while ((obj = enumerator.nextObject)) {
        NSLog(@"%@", obj);
    }
    
    NSLog(@"------------------------------------");
    
    LinkedList *list = [[LinkedList alloc] init];
    [list addItem:@"A"];
    [list addItem:@"B"];
    [list addItem:@"C"];
    [list addItem:@"D"];
    
    LinkedListIterator *iterator = [LinkedListIterator linkedListIteratorWithLinkedList:list];
    Node *node;
    while ((node = iterator.nextObject)) {
        NSLog(@"%@", node.nodeName);
    }
}


@end
複製代碼

結果展現

2019-04-07 23:00:13.950111+0800 IteratorPattern[10885:90208] A
2019-04-07 23:00:13.950321+0800 IteratorPattern[10885:90208] B
2019-04-07 23:00:13.950442+0800 IteratorPattern[10885:90208] C
2019-04-07 23:00:13.950584+0800 IteratorPattern[10885:90208] D
2019-04-07 23:00:13.950706+0800 IteratorPattern[10885:90208] ------------------------------------
2019-04-07 23:00:13.950840+0800 IteratorPattern[10885:90208] A
2019-04-07 23:00:13.951111+0800 IteratorPattern[10885:90208] B
2019-04-07 23:00:13.951952+0800 IteratorPattern[10885:90208] C
2019-04-07 23:00:13.953016+0800 IteratorPattern[10885:90208] D
複製代碼

5.總結

蘋果公司用本身的命名規則"枚舉器/枚舉"改寫了迭代器模式,用於相關基礎類的各類方法.

基礎框架中的NSEnumerator類實現了迭代器模式. NSArrayNSSetNSDictionary這樣的集合類,定義了返回與集合的類型相應的NSEnumerator子類實例的方法.

迭代器模式訪問者模式有些相似,尤爲是把遍歷算法放到訪問者模式中或者在遍歷聚合體時讓內部迭代器對元素執行操做的時候.組合模式經常依靠迭代器來遍歷其遞歸結構.多態的迭代器依靠工廠方法來實例化適當的迭代器具體子類.

行爲擴展

訪問者

1.概述

表示一個做用於某個對象結構中的各元素的操做.它讓咱們能夠在不改變各元素的類的前提下定義做用於這些元素的新操做.

2.使用場景

  • 一個複雜的對象結構包含不少其餘對象,他們有不一樣的接口(好比組合體),可是想對這些對象實施一些依賴於其具體類型的操做.
  • 須要對一個組合結構中的對象進行不少不相關的操做,可是不想讓這些操做"污染"這些對象的類.能夠將相關的操做集中起來,定義在一個訪問者類中,並在須要在訪問者中定義的操做時使用它.
  • 定義複雜結構的類不多做修改,但常常須要向其添加新的操做.

3.結構UML圖

4.代碼實例--在屏幕上塗鴉,把手指滑動的軌跡繪製出來

協議類Mark

// 不論線條仍是點,其實都是在介質上留下的標誌(Mark),它爲全部具體類定義了屬性和方法
@protocol Mark <NSObject>

@property (nonatomic, assign) CGPoint location;
@property (nonatomic, assign) CGSize size;
@property (nonatomic, strong) id<Mark> lastChild;

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

- (void)acceptMarkVisitor:(id<MarkVisitor>)visitor;

@end
複製代碼

組件類點Dot

// 點,組件只有一個點,那麼它會表現爲一個實心圓,在屏幕上表明一個點
@interface Dot : NSObject<Mark>

@end

@implementation Dot

@synthesize location;

@synthesize lastChild;

@synthesize size;

- (void)addMark:(id<Mark>)mark {}

- (void)removeMark:(id<Mark>)mark {}

- (id<Mark>)lastChild {
    return nil;
}

- (void)acceptMarkVisitor:(id<MarkVisitor>)visitor {
    [visitor visitDot:self];
}

@end
複製代碼

組件類頂點Vertex

// 頂點,鏈接起來的一串頂點,被繪製成鏈接起來的線條
@interface Vertex : NSObject<Mark>

@end

@implementation Vertex

@synthesize location;

@synthesize lastChild;

@synthesize size;

- (void)addMark:(id<Mark>)mark {}

- (void)removeMark:(id<Mark>)mark {}

- (id<Mark>)lastChild {
    return nil;
}

- (void)acceptMarkVisitor:(id<MarkVisitor>)visitor {
    [visitor visitVertex:self];
}

@end
複製代碼

組件類線條Stroke

@interface Stroke ()

@property (nonatomic, strong) NSMutableArray<id<Mark>> *children;

@end

@implementation Stroke

@dynamic location;

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

- (void)addMark:(id<Mark>)mark {
    [_children addObject:mark];
}

- (void)removeMark:(id<Mark>)mark {
    [_children removeObject:mark];
}

- (void)setLocation:(CGPoint)location {
    
}

- (CGPoint)location {
    if (_children.count == 0) {
        return CGPointZero;
    } else {
        return self.children.firstObject.location;
    }
}

- (id<Mark>)lastChild {
    return _children.lastObject;
}

- (void)acceptMarkVisitor:(id<MarkVisitor>)visitor {
    for (id<Mark> dot in self.children) {
        [dot acceptMarkVisitor:visitor];
    }
    
    [visitor visitStroke:self];
}

@end
複製代碼

定義訪問者接口類MarkVisitor

// 訪問者接口
@protocol MarkVisitor <NSObject>

- (void)visitMark:(id<Mark>)mark;
- (void)visitVertex:(Vertex *)vertex;
- (void)visitDot:(Dot *)dot;
- (void)visitStroke:(Stroke *)stroke;

@end
複製代碼

定義繪製類MarkRenderer

// 具體的訪問者,MarkRenderer繪製訪問者,它是對這些點和先進行繪製操做的
@interface MarkRenderer : NSObject<MarkVisitor>

- (instancetype)initWithCGContext:(CGContextRef)context;

@end

@interface MarkRenderer ()

@property (nonatomic, assign) CGContextRef context;
@property (nonatomic, assign) BOOL shouldMoveContextToDot;

@end

@implementation MarkRenderer

- (instancetype)initWithCGContext:(CGContextRef)context {
    if (self = [super init]) {
        self.context = context;
        self.shouldMoveContextToDot = YES;
    }
    return self;
}

- (void)visitMark:(id<Mark>)mark {
    
}

- (void)visitVertex:(Vertex *)vertex {
    CGFloat x = vertex.location.x;
    CGFloat y = vertex.location.y;
    if (self.shouldMoveContextToDot) {
        CGContextMoveToPoint(self.context, x, y);
        self.shouldMoveContextToDot = NO;
    } else {
        CGContextAddLineToPoint(self.context, x, y);
    }
}

- (void)visitDot:(Dot *)dot {
    CGFloat x = dot.location.x;
    CGFloat y = dot.location.y;
    CGRect frame = CGRectMake(x, y, 2, 2);
    
    CGContextSetFillColorWithColor(self.context, [[UIColor blackColor] CGColor]);
    CGContextFillEllipseInRect(self.context, frame);
}

- (void)visitStroke:(Stroke *)stroke {
    CGContextSetStrokeColorWithColor(self.context, [UIColor blueColor].CGColor);
    CGContextSetLineWidth(self.context, 1);
    CGContextSetLineCap(self.context, kCGLineCapRound);
    CGContextStrokePath(self.context);
    self.shouldMoveContextToDot = YES;
}

@end
複製代碼

定義用於展現的視圖CanvasView

@interface CanvasView : UIView

@property (nonatomic, strong) id<Mark> mark;

@end

@implementation CanvasView

- (void)setMark:(id<Mark>)mark {
    _mark = mark;
    
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    MarkRenderer *markRender = [[MarkRenderer alloc] initWithCGContext:context];
    [self.mark acceptMarkVisitor:markRender];
}

@end
複製代碼

控制器實現

@interface ViewController ()

@property (nonatomic, strong) id<Mark> parentMark;
@property (nonatomic, strong) CanvasView *canvasView;
@property (nonatomic, assign) CGPoint startPoint;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.parentMark = [[Stroke alloc] init];
    [self setupView];
}

- (void)setupView
{
    CanvasView *canvasView = [[CanvasView alloc] initWithFrame:self.view.frame];
    canvasView.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:canvasView];
    self.canvasView = canvasView;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.startPoint = [[touches anyObject] locationInView:self.canvasView];
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    CGPoint lastPoint = [[touches anyObject] previousLocationInView:self.canvasView];
    if (CGPointEqualToPoint(lastPoint, self.startPoint)) {
        id <Mark> newStroke = [[Stroke alloc] init];
        [self addMark:newStroke shouldAddToPreviousMark:NO];
    }
    
    CGPoint currentPoint  = [[touches anyObject] locationInView:self.canvasView];
    Vertex *vertex = [[Vertex alloc] init];
    vertex.location = currentPoint;
    [self addMark:vertex shouldAddToPreviousMark:vertex];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    CGPoint lastPoint = [[touches anyObject] previousLocationInView:self.canvasView];
    CGPoint currentPoint = [[touches anyObject] locationInView:self.canvasView];
    if (CGPointEqualToPoint(lastPoint, currentPoint))
    {
        Dot *singleDot = [[Dot alloc] init];
        singleDot.location = currentPoint;
        [self addMark:singleDot shouldAddToPreviousMark:NO];
    }
    self.startPoint = CGPointZero;
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.startPoint = CGPointZero;
}

- (void)addMark:(id<Mark>)mark shouldAddToPreviousMark:(BOOL)shouldAddToPreviousMark
{
    if (shouldAddToPreviousMark) {
        [self.parentMark.lastChild addMark:mark];
    } else {
        [self.parentMark addMark:mark];
    }
    
    self.canvasView.mark = self.parentMark;
}

@end
複製代碼

5.總結

訪問者模式是擴展組合結構功能的一種強有力的方式.若是組合結構具備精心設計的基本操做,並且結構未來也不會變動,就可使用訪問者模式,用各類不一樣用途的訪問者,以一樣的方式訪問這個組合結構.訪問者模式用盡量少的修改,能夠把組合結構與其餘訪問者類中的相關算法分離.

裝飾

1.概述

  • 持有對象的引用,不改變原始類,動態拓展對象的功能,不改變使用繼承的情形
  • 動態地給一個對象添加一些額外的的職責.就擴展功能來講,裝飾模式相比生成子類更爲靈活.

2.使用場景

  • 想要在不影響其餘對象的狀況下,以動態、透明的方式給單個對象添加職責.
  • 想要擴展一個類的行爲,卻作不到.類定義可能被隱藏,沒法進行子類化;或者,對類的每一個行爲的擴展,爲支持每種功能組合,將產生大量的子類.
  • 對類的職責的擴展是可選的.

3.結構UML圖

4.代碼實例--爲圖片添加濾鏡

普通實現

設置協議的抽象(咱們想讓全部的ImageComponent都能支持UIImage的重畫方法)

@protocol ImageComponent <NSObject>

@optional

- (void)drawAtPoint:(CGPoint)point;
- (void)drawAtPoint:(CGPoint)point blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;
- (void)drawInRect:(CGRect)rect;
- (void)drawInRect:(CGRect)rect blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;
- (void)drawAsPatternInRect:(CGRect)rect;

@end
複製代碼

設置核心的裝飾類,也是濾鏡基類

@interface ImageFilter : NSObject <ImageComponent>
{
    @private
    id <ImageComponent> component_;
}

/// 保持一個ImageComponent的引用,這個引用會被其餘具體的裝飾器裝飾.
@property (nonatomic, strong) id<ImageComponent> component;

- (void)apply;
- (id)initWithImageComponent:(id<ImageComponent>)component;
- (id)forwardingTargetForSelector:(SEL)aSelector;
複製代碼
- (id)initWithImageComponent:(id<ImageComponent>)component {
    if (self = [super init]) {
        [self setComponent:component];
    }
    
    return self;
}

- (void)apply {
    // 應該由子類重載,應用真正的濾鏡
}

/// 截獲消息
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSString *selectorName = NSStringFromSelector(aSelector);
    if ([selectorName hasPrefix:@"draw"]) {
        [self apply];
    }
    
    return component_;
}
複製代碼

設置仿射變換濾鏡

- (instancetype)initWithImageComponent:(id<ImageComponent>)component
                             transform:(CGAffineTransform)transform {
    if (self = [super initWithImageComponent:component]) {
        [self setTransform:transform];
    }
    
    return self;
}

- (void)apply {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextConcatCTM(context, transform_);
}

複製代碼

設置陰影濾鏡

- (void)apply {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGSize offset = CGSizeMake(-25, 15);
    CGContextSetShadow(context, offset, 20.0);
}
複製代碼

視圖展現配置

- (void)setImage:(UIImage *)image {
    image_ = image;
}

- (void)drawRect:(CGRect)rect {
    [image_ drawInRect:rect];
}
複製代碼

控制器配置

UIImage *img = [UIImage imageNamed:@"13.jpeg"];
    CGAffineTransform rotateTransform = CGAffineTransformMakeRotation(-M_PI / 4.0);
    CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(-img.size.width / 2.0, img.size.height / 8.0);
    CGAffineTransform finalTransform = CGAffineTransformConcat(rotateTransform, translateTransform);
    
    id<ImageComponent>transformedImage = [[ImageTransformFilter alloc] initWithImageComponent:img transform:finalTransform];
    id<ImageComponent>finalImage = [[ImageShadowFilter alloc] initWithImageComponent:transformedImage];
    
    DecoratorView *decoratorView = [[DecoratorView alloc] initWithFrame:self.view.bounds];
    [decoratorView setImage:(UIImage *)finalImage];
    [self.view addSubview:decoratorView];
複製代碼
  • iOS特有實現

設置基礎濾鏡配置方法

@interface UIImage (BaseFilter)

- (CGContextRef)beginContext;
- (UIImage *)getImageFromCurrentImageContext;
- (void)endContext;

@end
複製代碼
@implementation UIImage (BaseFilter)

- (CGContextRef)beginContext {
    CGSize size = [self size];
    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    return context;
}

- (UIImage *)getImageFromCurrentImageContext {
    [self drawAtPoint:CGPointZero];
    
    UIImage *imageOut = UIGraphicsGetImageFromCurrentImageContext();
    return imageOut;
}

- (void)endContext {
    UIGraphicsEndImageContext();
}

@end
複製代碼

設置仿射變換濾鏡方法

@implementation UIImage (Transform)

- (UIImage *)imageWithTransform:(CGAffineTransform)transform {
    CGContextRef context = [self beginContext];
    CGContextConcatCTM(context, transform);
    UIImage *imageOut = [self getImageFromCurrentImageContext];
    [self endContext];
    return imageOut;
}

@end
複製代碼

設置陰影濾鏡方法

@implementation UIImage (Shadow)

- (UIImage *)imageWithDropShadow {
    CGContextRef context = [self beginContext];
    CGSize offset = CGSizeMake(-25, 15);
    CGContextSetShadow(context, offset, 20.0);
    
    UIImage *imageOut = [self getImageFromCurrentImageContext];
    [self endContext];
    return imageOut;
}

@end
複製代碼

控制器配置

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIImage *img = [UIImage imageNamed:@"13.jpeg"];
    CGAffineTransform rotateTransform = CGAffineTransformMakeRotation(-M_PI / 4.0);
    CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(-img.size.width / 2.0, img.size.height / 8.0);
    CGAffineTransform finalTransform = CGAffineTransformConcat(rotateTransform, translateTransform);
    
    UIImage *transformedImage = [img imageWithTransform:finalTransform];
    UIImage *finalImage = [transformedImage imageWithDropShadow];

    DecoratorView *decoratorView = [[DecoratorView alloc] initWithFrame:self.view.bounds];
    [decoratorView setImage:(UIImage *)finalImage];
    [self.view addSubview:decoratorView];
}
複製代碼

5.總結

優勢:

  • 把類中的裝飾功能從類中搬移去除,這樣能夠簡化原有的類
  • 有效地把類的核心職責和裝飾功能區分開了.並且能夠去除相關類中重複的裝飾邏輯.

裝飾模式在OC中會有不一樣的實現方式即爲範疇.真正子類方式的實際使用是一種較爲結構化的方式鏈接各類裝飾器.範疇的方式更爲簡單和輕量,適用於現有類只須要少許裝飾器的應用程序.

責任鏈

1.概述

使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間發生耦合.此模式將這些對象連成一條鏈,並沿着這條鏈傳遞請求,直到有一個對象處理它爲止.

2.使用場景

  • 有多個對象能夠處理請求,而處理請求只有在運行時才能肯定.
  • 向一組對象發出請求,而不想顯式指定處理請求的特定處理程序.

3.結構UML圖

責任鏈類圖

請求處理典型結構圖

4.代碼實例--對文本進行判斷.

原文:iOS-Design-Patterns

接口處理請求的協議ChainOfResponsibilityProtocol

@protocol ChainOfResponsibilityProtocol <NSObject>

@property (nonatomic, strong) id<ChainOfResponsibilityProtocol> successor;

- (void)handlerRequest:(id)request;

@end
複製代碼

定義鏈頭類HeadChain

/// 鏈頭
@interface HeadChain : NSObject<ChainOfResponsibilityProtocol>

@end

@interface HeadChain ()

@property (nonatomic, weak) id<ChainOfResponsibilityProtocol> nextSuccessor;

@end

@implementation HeadChain

@synthesize successor;

- (void)setSuccessor:(id<ChainOfResponsibilityProtocol>)successor {
    self.nextSuccessor = successor;
}

- (id<ChainOfResponsibilityProtocol>)successor {
    return self.nextSuccessor;
}

- (void)handlerRequest:(id)request {
    [self.successor handlerRequest:request];
}

@end
複製代碼

定義驗證手機號碼節點類PhoneNumChain

/// 驗證手機號碼節點
@interface PhoneNumChain : NSObject<ChainOfResponsibilityProtocol>

@end

@interface PhoneNumChain ()

@property (nonatomic, weak) id<ChainOfResponsibilityProtocol> nextSuccessor;

@end

@implementation PhoneNumChain

@synthesize successor;

- (void)setSuccessor:(id<ChainOfResponsibilityProtocol>)successor {
    self.nextSuccessor = successor;
}

- (id<ChainOfResponsibilityProtocol>)successor {
    return self.nextSuccessor;
}

- (void)handlerRequest:(id)request {
    NSString *str = request;
    if ([str isKindOfClass:[NSString class]] && str.length > 0) {
        // 匹配電話號碼(手機號以13, 15,18開頭,八個 \d 數字字符)
        BOOL isMatch = [str isMatch:RX(@"^((13[0-9])|(15[^4,\\D])|(18[0,0-9]))\\d{8}$")];
        if (isMatch) {
            NSLog(@"%@ is a PhoneNum", str);
        } else {
            [self.successor handlerRequest:request];
        }
    } else {
        [self.successor handlerRequest:request];
    }
}

@end
複製代碼

驗證郵箱節點類EmailChain

/// 驗證郵箱節點
@interface EmailChain : NSObject<ChainOfResponsibilityProtocol>

@end

@interface EmailChain ()

@property (nonatomic, weak) id<ChainOfResponsibilityProtocol> nextSuccessor;

@end

@implementation EmailChain

@synthesize successor;

- (void)setSuccessor:(id<ChainOfResponsibilityProtocol>)successor {
    self.nextSuccessor = successor;
}

- (id<ChainOfResponsibilityProtocol>)successor {
    return self.nextSuccessor;
}

- (void)handlerRequest:(id)request {
    NSString *str = request;
    if ([str isKindOfClass:[NSString class]] && str.length > 0) {
        // 匹配郵箱
        BOOL isMatch = [str isMatch:RX(@"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}")];
        if (isMatch) {
            NSLog(@"%@ is a Email", str);
        } else {
            [self.successor handlerRequest:request];
        }
    } else {
        [self.successor handlerRequest:request];
    }
}

@end
複製代碼

驗證用戶名節點類UserNameChain

/// 驗證用戶名節點
@interface UserNameChain : NSObject<ChainOfResponsibilityProtocol>

@end

@interface UserNameChain ()

@property (nonatomic, weak) id<ChainOfResponsibilityProtocol> nextSuccessor;

@end

@implementation UserNameChain

@synthesize successor;

- (void)setSuccessor:(id<ChainOfResponsibilityProtocol>)successor {
    self.nextSuccessor = successor;
}

- (id<ChainOfResponsibilityProtocol>)successor {
    return self.nextSuccessor;
}

- (void)handlerRequest:(id)request {
    NSString *str = request;
    if ([str isKindOfClass:[NSString class]] && str.length > 0) {
        // 匹配用戶名(用戶名長度爲6-20位之間,大小寫字母或者數字都可)
        BOOL isMatch = [str isMatch:RX(@"^[A-Za-z0-9]{6,20}+$")];
        if (isMatch) {
            NSLog(@"%@ is a UserName", str);
        } else {
            [self.successor handlerRequest:request];
        }
    } else {
        [self.successor handlerRequest:request];
    }
}

@end
複製代碼

鏈尾類EndChain

/// 鏈尾
@interface EndChain : NSObject<ChainOfResponsibilityProtocol>

@end

@interface EndChain ()

@property (nonatomic, weak) id<ChainOfResponsibilityProtocol> nextSuccessor;

@end

@implementation EndChain

@synthesize successor;

- (void)setSuccessor:(id<ChainOfResponsibilityProtocol>)successor {
}

- (id<ChainOfResponsibilityProtocol>)successor {
    return self;
}

- (void)handlerRequest:(id)request {
    NSLog(@"沒法處理該請求");
}

@end

複製代碼

控制器實現

// 設置各請求鏈條
    HeadChain *headChain = [[HeadChain alloc] init];
    PhoneNumChain *phoneNumChain = [[PhoneNumChain alloc] init];
    EmailChain *emailChain = [[EmailChain alloc] init];
    UserNameChain *userNameChain = [[UserNameChain alloc] init];
    EndChain *endChain = [[EndChain alloc] init];
    
    // 設置下一連接點
    headChain.successor = phoneNumChain;
    phoneNumChain.successor = emailChain;
    emailChain.successor = userNameChain;
    userNameChain.successor = endChain;
    
    // 處理事件
    [headChain handlerRequest:@"18659007343"];
    [headChain handlerRequest:@"18659007343@qq.com"];
    [headChain handlerRequest:@"abc1865900"];
    [headChain handlerRequest:@"====="];
複製代碼

結果展現

2019-04-17 00:26:21.661847+0800 ChainOfResponsibilityPattern[84339:1697824] 18659007343 is a PhoneNum
2019-04-17 00:26:21.662235+0800 ChainOfResponsibilityPattern[84339:1697824] 18659007343@qq.com is a Email
2019-04-17 00:26:21.662562+0800 ChainOfResponsibilityPattern[84339:1697824] abc1865900 is a UserName
2019-04-17 00:26:21.662889+0800 ChainOfResponsibilityPattern[84339:1697824] 沒法處理該請求
複製代碼

5.總結

CocoaUIResponder類就是責任鏈的一個實例.當操做應用時,進行點擊,點擊操做經過責任鏈一步步走下去直到找到相對應要響應的UIResponder.

算法封裝

模板方法

1.概述

定義一個操做中算法的骨架,而將一些步驟延遲到子類中.模板方法使子類能夠衝定義算法的某些特定步驟而不改變該算法的結構.

2.使用場景

  • 須要一次性實現算法的不一樣部分,並將可變的行爲留給子類來實現.
  • 子類的共同行爲應該被提取出來放到公共類中,以免代碼重複.現有代碼的差異應該被分離爲新的操做.而後用一個調用這些新操做的模板方法來替換這些不一樣的代碼.
  • 須要控制子類的擴展.能夠定義一個在特定點調用"鉤子"操做的模板方法.子類能夠經過對鉤子操做的實如今這些點擴展功能.

3.結構UML圖

4.代碼實例--利用模板方法制做三明治

定義基礎類AnySandwich

@interface AnySandwich : NSObject

- (void)make;
- (void)prepareBread;
- (void)putBreadOnPlate;
- (void)addMeat;
- (void)addCondiments;
- (void)serve;
- (void)extraStep;

@end
複製代碼

內部實現製做三明治的基本流程. extraStep爲鉤子方法,子類可實現也能夠不用實現. prepareBread addMeat addCondiments的具體實現交由子類.如若沒有實現,將libc++abi.dylib: terminating with uncaught exception of type NSException運行失敗來限制子類.

@implementation AnySandwich

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

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

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

#pragma mark -
#pragma Detail will be handled by subclasses

- (void)prepareBread {
    // 要保證子類重載這個方法
    [NSException raise:NSInternalInconsistencyException
                format:@"You must override%@in a subclass", NSStringFromSelector(_cmd)];
}

- (void)addMeat {
    // 要保證子類重載這個方法
    [NSException raise:NSInternalInconsistencyException
                format:@"You must override%@in a subclass", NSStringFromSelector(_cmd)];
}

- (void)addCondiments {
    // 要保證子類重載這個方法
    [NSException raise:NSInternalInconsistencyException
                format:@"You must override%@in a subclass", NSStringFromSelector(_cmd)];
}

@end

複製代碼

ReubenSandwich實現

@implementation ReubenSandwich

- (void)prepareBread {
    [self cutRyeBread];
}

- (void)addMeat {
    [self addCornBeef];
}

- (void)addCondiments {
    [self addSauerkraut];
    [self addThousandIslandDressing];
    [self addSwissCheese];
}

- (void)extraStep {
    [self grillIt];
}

#pragma mark -
#pragma mark ReubenSandwich Specific Methods

- (void)cutRyeBread {
    // 魯賓三明治須要兩片黑麥麪包
}

- (void)addCornBeef {
    // 魯賓三明治加大量醃牛肉
}

- (void)addSauerkraut {
    // 加入德國酸菜
}

- (void)addThousandIslandDressing {
    // 加入千島醬
}

- (void)addSwissCheese {
    // 加入上等瑞士奶酪
}

- (void)grillIt {
    // 最後要把它烤一下
}

@end
複製代碼

5.總結

模板方法是代碼複用的基本技術,是抽出共同行爲放入框架類中的手段.這一方式有助於提升可擴展性與可複用性,而維持各類類之間的鬆耦合.

策略

1.概述

  • 定義一系列算法,把它們一個個封裝起來,而且使它們可互相替換.
  • 本模式使得算法可獨立於使用它的客戶而變化.
  • MVC:控制器與視圖之間是一種基於策略模式的關係.

2.使用場景

  • 一個類在其操做中使用多個條件語句來定義許多行爲.咱們能夠把相關的條件分支移到它們本身的策略類中.
  • 須要算法的各類變體.
  • 須要避免把複雜的、與算法相關的數據結構暴露給客戶端.
  • 若是代碼有不少條件語句,就可能意味着須要把它們重構成各類策略對象.

3.結構UML圖

4.代碼實例--驗證字符串實現方式

建立算法抽象基類,開放驗證接口並實現

@interface StringValidator : NSObject

- (BOOL)validateString:(NSString *)str error:(NSError **)error;

@end


@implementation StringValidator

- (BOOL)validateString:(NSString *)str error:(NSError * _Nullable __autoreleasing *)error {
    if (error) {
        *error = nil;
    }
    return NO;
}

@end
複製代碼

建立相關的算法繼承類 NumbericValidator數值判斷類

@interface NumbericValidator : StringValidator

- (BOOL)validateString:(NSString *)str error:(NSError * _Nullable __autoreleasing *)error;

@end

@implementation NumbericValidator

- (BOOL)validateString:(NSString *)str error:(NSError *__autoreleasing  _Nullable *)error {
    NSError *regError = nil;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^[0-9]*$" options:NSRegularExpressionAnchorsMatchLines error:&regError];
    NSUInteger numberOfMatchs = [regex numberOfMatchesInString:str options:NSMatchingAnchored range:NSMakeRange(0, str.length)];
    if (numberOfMatchs == 0) {
        if (error != nil) {
            NSString *description = NSLocalizedString(@"Failed", @"");
            NSString *reason = NSLocalizedString(@"can contain only numberical", @"");
            NSArray *objArray = [NSArray arrayWithObjects:description, reason, nil];
            NSArray *keyArray = [NSArray arrayWithObjects:NSLocalizedDescriptionKey, NSLocalizedFailureReasonErrorKey, nil];
            NSDictionary *userInfo = [NSDictionary dictionaryWithObjects:objArray forKeys:keyArray];
            *error = [NSError errorWithDomain:@"StringErrorDomain" code:1001 userInfo:userInfo];
        }
        return NO;
    }
    return YES;
}

@end
複製代碼

AlphaValidator字母判斷類

@interface AlphaValidator : StringValidator

- (BOOL)validateString:(NSString *)str error:(NSError * _Nullable __autoreleasing *)error;

@end

@implementation AlphaValidator

- (BOOL)validateString:(NSString *)str error:(NSError *__autoreleasing  _Nullable *)error {
    NSError *regError = nil;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^[a-zA-Z]*$" options:NSRegularExpressionAnchorsMatchLines error:&regError];
    NSUInteger numberOfMatchs = [regex numberOfMatchesInString:str options:NSMatchingAnchored range:NSMakeRange(0, str.length)];
    if (numberOfMatchs == 0) {
        if (error != nil) {
            NSString *description = NSLocalizedString(@"Failed", @"");
            NSString *reason = NSLocalizedString(@"can contain only alpha", @"");
            NSArray *objArray = [NSArray arrayWithObjects:description, reason, nil];
            NSArray *keyArray = [NSArray arrayWithObjects:NSLocalizedDescriptionKey, NSLocalizedFailureReasonErrorKey, nil];
            NSDictionary *userInfo = [NSDictionary dictionaryWithObjects:objArray forKeys:keyArray];
            *error = [NSError errorWithDomain:@"StringErrorDomain" code:1002 userInfo:userInfo];
        }
        return NO;
    }
    return YES;
}
@end

複製代碼

算法策略類StringValidatorManager

@interface StringValidatorManager : NSObject

+ (BOOL)numbervalidString:(NSString *)str error:(NSError **)error;
+ (BOOL)alphaString:(NSString *)str error:(NSError **)error;

@end


@implementation StringValidatorManager

+ (BOOL)numbervalidString:(NSString *)str error:(NSError * _Nullable __autoreleasing *)error {
    NumbericValidator *number = [[NumbericValidator alloc] init];
    return [number validateString:str error:error];
}

+ (BOOL)alphaString:(NSString *)str error:(NSError * _Nullable __autoreleasing *)error {
    AlphaValidator *alpha = [[AlphaValidator alloc] init];
    return [alpha validateString:str error:error];
}

@end
複製代碼

控制器實現

NSString *test1 = @"112";
    NSError *error0;
    [StringValidatorManager numbervalidString:test1 error:&error0];
    if (error0) {
        NSLog(@"0: %@", error0.localizedFailureReason);
    }
    
    NSError *error1;
    [StringValidatorManager alphaString:test1 error:&error1];
    if (error1) {
        NSLog(@"1: %@", error1.localizedFailureReason);
    }
    
    NSString *test2 = @"adsf";
    NSError *error2;
    [StringValidatorManager numbervalidString:test2 error:&error2];
    if (error2) {
        NSLog(@"2: %@", error2.localizedFailureReason);
    }
    
    NSError *error3;
    [StringValidatorManager alphaString:test2 error:&error3];
    if (error3) {
        NSLog(@"3: %@", error3.localizedFailureReason);
    }
複製代碼

結果展現

2018-12-25 22:22:45.027745+0800 StrategyPattern[21641:845119] 1: can contain only alpha
2018-12-25 22:22:45.028076+0800 StrategyPattern[21641:845119] 2: can contain only numberical
複製代碼

5.總結

優勢

  • 策略模式提供了對「開閉原則」的完美支持,用戶能夠在不修改原有系統的基礎上選擇算法或行爲,也能夠靈活地增長新的算法或行爲.
  • 策略模式提供了管理相關的算法族的辦法.
  • 策略模式提供了能夠替換繼承關係的辦法.
  • 使用策略模式能夠避免使用多重條件轉移語句.

策略模式裝飾模式有些類似.裝飾器從外部擴展對象的行爲,而各類策略則被封裝在對象之中.因此說裝飾器改變對象的"外表"而策略改變對象的"內容".

命令

1.概述

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

2.使用場景

  • 想讓應用程序支持撤銷與恢復.
  • 想用對象參數化一個動做以執行操做,並用不一樣命令對象來代替回調函數.
  • 想要在不一樣時刻對請求進行指定、排列和執行.
  • 想記錄修改日誌,這樣在系統故障時,這些修改可在後來重作一遍.
  • 想讓系統支持事務,事務封裝了對數據的一系列修改.事務能夠建模爲命令對象.

3.結構UML圖

4.代碼實例--發送命令控制燈和CD播放器的開關.

原文:設計模式系列 6-- 命令模式

Client建立ConcreteCommand對象並設定器receiver; Invoker要求通用命令(其實是ConcreteCommand)實施請求; Command要爲Invoker所知的通用接口; ConcreteCommandReceiver和對它的操做action之間的中間人做用; Receiver能夠隨着由Command(ConcreteCommand)對象實施的相應請求,而執行實際操做的任何對象.

接口協議CommandInterface

/// 命令對象的公共接口(按鈕執行的動做)
@protocol CommandInterface <NSObject>

/// 執行命令
- (void)execute;

/// 撤銷
- (void)undo;

@end
複製代碼

定義燈類Light

/// 電燈類
@interface Light : NSObject

- (void)lightOn;
- (void)lightOff;

@end

@implementation Light

- (void)lightOn {
    NSLog(@"開燈");
}

- (void)lightOff {
    NSLog(@"關燈");
}

@end
複製代碼

定義CD播放器類CDPlayer

/// CD播放器類
@interface CDPlayer : NSObject

- (void)CDPlayerOn;
- (void)CDPlayerOff;

@end

@implementation CDPlayer

- (void)CDPlayerOn {
    NSLog(@"打開CD播放器");
}

- (void)CDPlayerOff {
    NSLog(@"關閉CD播放器");
}

@end
複製代碼

打開燈命令類LightOnCommand

/// 打開燈命令類
@interface LightOnCommand : NSObject<CommandInterface>

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

@end

@implementation LightOnCommand

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

- (void)execute {
    [self.light lightOn];
}

- (void)undo {
    [self.light lightOff];
}

@end
複製代碼

關燈命令類LightOffCommand

/// 關燈命令類
@interface LightOffCommand : NSObject<CommandInterface>

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

@end

@implementation LightOffCommand

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

- (void)execute {
    [self.light lightOff];
}

- (void)undo {
    [self.light lightOn];
}

@end
複製代碼

CD播放器播放類CDPlayerOnCommand

/// CD播放器播放類
@interface CDPlayerOnCommand : NSObject<CommandInterface>

- (instancetype)initWithCDPlayer:(CDPlayer *)cdPlayer;

@end

@implementation CDPlayerOnCommand

- (instancetype)initWithCDPlayer:(CDPlayer *)cdPlayer {
    if (self = [super init]) {
        self.cdPlayer = cdPlayer;
    }
    return self;
}

- (void)execute {
    [self.cdPlayer CDPlayerOn];
}

- (void)undo {
    [self.cdPlayer CDPlayerOff];
}

@end
複製代碼

定義CD播放器關閉類CDPlayerOffCommand

/// CD播放器關閉類
@interface CDPlayerOffCommand : NSObject<CommandInterface>

- (instancetype)initWithCDPlayer:(CDPlayer *)cdPlayer;

@end

@implementation CDPlayerOffCommand

- (instancetype)initWithCDPlayer:(CDPlayer *)cdPlayer {
    if (self = [super init]) {
        self.cdPlayer = cdPlayer;
    }
    return self;
}

- (void)execute {
    [self.cdPlayer CDPlayerOff];
}

- (void)undo {
    [self.cdPlayer CDPlayerOn];
}

@end
複製代碼

定義遙控器類RemoteControl

/// 命令調用者(遙控器)
@interface RemoteControl : NSObject

//@property (nonatomic, strong) id<CommandInterface> slot;

- (void)onClickWithIdx:(NSInteger)idx;
- (void)offClickWithIdx:(NSInteger)idx;

- (void)setCommandWithIdx:(NSInteger)idx
                onCommand:(id<CommandInterface>)onCommand
               offCommand:(id<CommandInterface>)offCommand;

/// 撤銷剛纔的操做
- (void)undoAction;

/// 撤銷全部操做
- (void)undoAllAction;

@end

@interface RemoteControl ()

@property (nonatomic, strong) NSArray<id<CommandInterface>> *onCommands;
@property (nonatomic, strong) NSArray<id<CommandInterface>> *offCommands;
@property (nonatomic, strong) id<CommandInterface> undoCommand;   ///< 上一次的命令
@property (nonatomic, strong) NSMutableArray<id<CommandInterface>> *completeCommandsArr;
@end

@implementation RemoteControl

- (instancetype)init {
    if (self = [super init]) {
        // 默認有4類命令類型
        NSMutableArray *mOnArr = [[NSMutableArray alloc] init];
        NSMutableArray *mOffArr = [[NSMutableArray alloc] init];
        for (int i = 0; i < 4; ++i) {
            [mOnArr addObject:[[DefaultCommand alloc] init]];
            [mOffArr addObject:[[DefaultCommand alloc] init]];
        }
        self.onCommands = mOnArr.copy;
        self.offCommands = mOnArr.copy;
        self.completeCommandsArr = [[NSMutableArray alloc] init];
    }
    return self;
}

- (void)onClickWithIdx:(NSInteger)idx {
    if (idx >= self.onCommands.count || idx < 0) {
        return;
    }
    
    [self.onCommands[idx] execute];
    self.undoCommand = self.onCommands[idx];
    [self.completeCommandsArr addObject:self.onCommands[idx]];
}

- (void)offClickWithIdx:(NSInteger)idx {
    if (idx >= self.offCommands.count || idx < 0) {
        return;
    }
    
    [self.offCommands[idx] execute];
    self.undoCommand = self.offCommands[idx];
    [self.completeCommandsArr addObject:self.offCommands[idx]];
}

- (void)undoAction {
    [self.undoCommand undo];
    [self.completeCommandsArr removeObject:self.undoCommand];
}

- (void)undoAllAction {
    [self.completeCommandsArr enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id<CommandInterface>  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [obj undo];
    }];
    
    [self.completeCommandsArr removeAllObjects];
}

- (void)setCommandWithIdx:(NSInteger)idx
                onCommand:(id<CommandInterface>)onCommand
               offCommand:(id<CommandInterface>)offCommand {
    if (idx < 0 || idx >= self.onCommands.count) {
        return;
    }
    
    if (idx < 0 || idx >= self.offCommands.count) {
        return;
    }
    
    NSMutableArray *mOnCommands = [self.onCommands mutableCopy];
    [mOnCommands replaceObjectAtIndex:idx withObject:onCommand];
    self.onCommands = mOnCommands;
    
    NSMutableArray *mOffCommands = [self.offCommands mutableCopy];
    [mOffCommands replaceObjectAtIndex:idx withObject:offCommand];
    self.offCommands = mOffCommands;
}

@end

複製代碼

定義初始的命令類

/// 默認命令類(設備默認狀態)
@interface DefaultCommand : NSObject<CommandInterface>

@end

@implementation DefaultCommand

- (void)execute {
    NSLog(@"默認命令狀態下");
}

- (void)undo {
    NSLog(@"默認撤銷");
}

@end

複製代碼

定義面板用於提供給外接接口

/// 命令裝配者(將命令安裝到遙控器上)
@interface RemoteLoader : NSObject

@property (nonatomic, strong, readonly) RemoteControl *rc;

- (instancetype)initWithRemoteControl:(RemoteControl *)rc;

@end

@implementation RemoteLoader

- (instancetype)initWithRemoteControl:(RemoteControl *)rc {
    if (self = [super init]) {
        self.rc = rc;
        [self configCommands];
    }
    return self;
}

- (void)configCommands {
    Light *light = [[Light alloc] init];
    LightOnCommand *lightOnCommand = [[LightOnCommand alloc] initWithLight:light];
    LightOffCommand *lightOffCommand = [[LightOffCommand alloc] initWithLight:light];
    [self.rc setCommandWithIdx:0 onCommand:lightOnCommand offCommand:lightOffCommand];
    
    CDPlayer *cd = [[CDPlayer alloc] init];
    CDPlayerOnCommand *cdPlayerOnCommand = [[CDPlayerOnCommand alloc] initWithCDPlayer:cd];
    CDPlayerOffCommand *cdPlayerOffCommand = [[CDPlayerOffCommand alloc] initWithCDPlayer:cd];
    [self.rc setCommandWithIdx:1 onCommand:cdPlayerOnCommand offCommand:cdPlayerOffCommand];
}

@end

複製代碼

控制器實現

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
 
    RemoteControl *rc = [[RemoteControl alloc] init];
    RemoteLoader *loader = [[RemoteLoader alloc] initWithRemoteControl:rc];
    [loader.rc onClickWithIdx:0];
    [loader.rc onClickWithIdx:1];
    [loader.rc offClickWithIdx:0];
    [loader.rc offClickWithIdx:1];
    
    NSLog(@"------------執行撤銷動做------------");
    [loader.rc undoAction];
    [loader.rc undoAllAction];
}

@end
複製代碼

結果展現

2019-04-14 21:54:05.762810+0800 CommandPattern[68418:1486836] 開燈
2019-04-14 21:54:05.762981+0800 CommandPattern[68418:1486836] 打開CD播放器
2019-04-14 21:54:05.763088+0800 CommandPattern[68418:1486836] 關燈
2019-04-14 21:54:05.763198+0800 CommandPattern[68418:1486836] 關閉CD播放器
2019-04-14 21:54:05.763315+0800 CommandPattern[68418:1486836] ------------執行撤銷動做------------
2019-04-14 21:54:05.763417+0800 CommandPattern[68418:1486836] 打開CD播放器
2019-04-14 21:54:05.763661+0800 CommandPattern[68418:1486836] 開燈
2019-04-14 21:54:05.763770+0800 CommandPattern[68418:1486836] 關閉CD播放器
2019-04-14 21:54:05.763886+0800 CommandPattern[68418:1486836] 關燈

複製代碼

5.總結

Cocoa Touch框架中使用命令模式的典型例子.

NSInvocation

NSUndoManager

性能與對象訪問

享元

1.概述

運用共享技術有效地支持大量細粒度的對象.

2.使用場景

  • 應用程序使用不少對象.
  • 在內存中保存對象會影響內存性能.
  • 對象的多數特有狀態(外在狀態)能夠放到外部而輕量化.
  • 移除了外在狀態以後,能夠用較少的共享對象替代原來的那組對象.
  • 應用程序不依賴於對象標識,由於共享對象不能提供惟一的標識.

3.結構UML圖

4.代碼實例--繪製幾百個花朵圖案.

原文:Objective-C設計模式解析-享元

花朵視圖FlowerView

// 繪製一朵花朵圖案
@interface FlowerView : UIImageView

@end

@implementation FlowerView

- (void)drawRect:(CGRect)rect {
    [self.image drawInRect:rect];
}

@end
複製代碼

花朵生成工廠類FlowerFactory.

`FlowerFactory`用`flowerPool`聚合了一個花朵池的引用.
`flowerPool`是一個保存`FlowerView`的全部實例的數據結構.
`FlowerFactory`經過`flowerViewWithType:`方法返回`FlowerView`
實例.
複製代碼
static NSInteger kTotalNumberOfFlowTypes = 7;

typedef NS_ENUM(NSInteger, FlowerType) {
    kAnemone = 0,
    kCosmos,
    kGerberas,
    kHollyhock,
    kJasmine,
    kZinnia
};

NS_ASSUME_NONNULL_BEGIN

@interface FlowerFactory : NSObject

- (UIView *)flowerViewWithType:(FlowerType)type;

@end

NS_ASSUME_NONNULL_END

@interface FlowerFactory ()

@property (nonatomic, strong) NSMutableDictionary *flowerPool;

@end

@implementation FlowerFactory

- (UIView *)flowerViewWithType:(FlowerType)type {
    UIView *flowerView = [_flowerPool objectForKey:@(type)];
    if (flowerView) {
        return flowerView;
    }
    
    NSString *imgName;
    switch (type) {
        case kAnemone:
            imgName = @"anemone";
            break;
        case kCosmos:
            imgName = @"cosmos";
            break;
        case kGerberas:
            imgName = @"gerberas";
            break;
        case kHollyhock:
            imgName = @"hollyhock";
            break;
        case kJasmine:
            imgName = @"jasmine";
            break;
        case kZinnia:
            imgName = @"zinnia";
            break;
        default:
            break;
    }
    
    UIImage *img = [UIImage imageNamed:imgName];
    if (!img) {
        return nil;
    }
    
    FlowerView *tmpView = [[FlowerView alloc] init];
    tmpView.image = img;
    [self.flowerPool setObject:tmpView forKey:@(type)];
    return tmpView;
}

- (NSMutableDictionary *)flowerPool {
    if (!_flowerPool) {
        NSMutableDictionary *mDic = [[NSMutableDictionary alloc] initWithCapacity:kTotalNumberOfFlowTypes];
        
        _flowerPool = mDic;
    }
    return _flowerPool;
}

@end
複製代碼

花朵外部數據結構ExtrinsicFlowerState

#ifndef ExtrinsicFlowerState_h
#define ExtrinsicFlowerState_h

struct ExtrinsicFlowerState {
    __unsafe_unretained UIView *flowerView;
    CGRect area;
};
複製代碼

花朵展現視圖FlowerContainerView

@interface FlowerContainerView : UIView

@property (nonatomic, strong) NSArray *flowerList;

@end

@implementation FlowerContainerView

- (void)drawRect:(CGRect)rect {
    for (NSValue *stateValue in self.flowerList) {
        struct ExtrinsicFlowerState state;
        [stateValue getValue:&state];

        UIView *flowerView = state.flowerView;
        CGRect frame = state.area;
        [flowerView drawRect:frame];
    }
}

@end
複製代碼

控制器實現

static NSInteger kFlowerListCount = 200;

@interface ViewController ()

@property (nonatomic, strong) FlowerFactory *flowerFactory;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    FlowerContainerView *containerView = [[FlowerContainerView alloc] initWithFrame:self.view.bounds];
    containerView.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:containerView];
    
    _flowerFactory = [[FlowerFactory alloc] init];
    NSMutableArray *flowerList = [[NSMutableArray alloc] initWithCapacity:kFlowerListCount];
    
    for (int i = 0; i < kFlowerListCount; ++i) {
        // 從工廠取得一個共享的花朵享元對象實例
        FlowerType type = arc4random() % kTotalNumberOfFlowTypes;
        UIView *flowerView = [_flowerFactory flowerViewWithType:type];
        
        // 設置花朵顯示的區域
        CGRect viewBounds = self.view.bounds;
        CGFloat x = arc4random() % (int)CGRectGetWidth(viewBounds);
        CGFloat y = arc4random() % (int)CGRectGetHeight(viewBounds);
        CGFloat minSize = 10;
        CGFloat maxSize = 60;
        CGFloat size = (arc4random() % (int)(maxSize - minSize)) + minSize;
        
        struct ExtrinsicFlowerState state;
        state.flowerView = flowerView;
        state.area = CGRectMake(x, y, size, size);
        
        [flowerList addObject:[NSValue value:&state withObjCType:@encode(struct ExtrinsicFlowerState)]];
    }
    
    [containerView setFlowerList:flowerList.copy];
}


@end
複製代碼

5.總結可以節省多少空間

經過享元對象可以節省的空間,取決於幾個因素:

  • 經過共享減小的對象總數;
  • 每一個對象中內在狀態(即,可共享的狀態)的數量;
  • 外在狀態是計算出來的仍是保存的;

然而,對共享對象外在狀態的傳遞、查找和計算,可能產生運行時的開銷,尤爲在外在狀態本來是做爲內在狀態來保存的時候.當享元的共享愈來愈多時,空間的節省會抵消這些開銷.共享的享元越多,節省的存儲就越多.節省直接跟共享的狀態相關.若是對象有大量內在和外在狀態,外在狀態又可以計算出來而不用存儲的時候,就能節省最大的空間.這樣咱們以兩種方式節省了存儲空間:共享減小了內在狀態的開銷,經過犧牲計算時間又節省了外在狀態的存儲空間.

代理

1.概述

爲其餘對象提供一種代理以控制對這個對象的訪問.

2.使用場景

  • 須要一個遠程代理,爲位於不一樣地址空間或網絡中的對象提供本地表明.
  • 須要一個虛擬代理,爲根據要求建立重型的對象.例如懶加載圖片.
  • 須要一個保護代理,來根據不一樣訪問權限控制對原對象的訪問.
  • 須要一個智能代理,經過對實體對象的引用進行計數來管理內存.也能用於鎖定實體對象,讓其餘對象不能修改它.

3.結構UML圖

4.代碼實例 --讓哆啦A夢幫忙送禮物

定義代理類SendGift

@protocol SendGift <NSObject>

- (void)sendGift;

@end
複製代碼

哆啦A夢

@interface Doraemon : NSObject<SendGift>

@end
複製代碼

僱主類

@interface Person : NSObject

@property (nonatomic, weak) id<SendGift> delegate;

@end
複製代碼

調用場景

Person *daxiong = [[Person alloc] init];
Doraemon *doraemon = [[Doraemon alloc] init];
// 哆啦A夢成爲代理,替大熊送禮物
daxiong.delegate = doraemon;
[daxiong.delegate sendGift];
複製代碼

5.總結

主要思想是使用一個基本上跟實體對象行爲相同的代理.客戶端能夠"透明地"使用代理,即沒必要知悉所面對的只是一個代理而不是實體對象.當客戶端請求某些建立的開銷較大的功能時,代理將把請求轉發給實體對象,準備好請求的功能並返回給客戶端.客戶端不知道幕後發生了什麼.

代理模式在OC中會經常會出現而且平常開發也會用到,最爲典型的例子就是Applegate類了,它被委託處理應用生命週期期間所面對的各類情況.

對象狀態

備忘錄

1.概述

在不破壞封裝的前提下,捕獲一個對象的內部狀態,並在該對象以外保存這個狀態.這樣之後就可將對象恢復到原先保存的狀態.

2.使用場景

  • 須要保存一個對象(或某部分)在某一個時刻的狀態,這樣之後就能夠恢復到先前的狀態.
  • 用於獲取狀態的接口會暴露實現的細節,須要將其隱藏起來.

3.結構UML圖

4.代碼實例--原發器建立一個包含其狀態的備忘錄,並傳給看管人.看管人不知如何與備忘錄交互,但會把備忘錄放在安全之處保管好.

定義原發器類Originator

@interface Originator : NSObject

@property (nonatomic, copy) NSString *address;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

- (Memento *)createMemento;
- (void)setMemento:(Memento *)memento;

@end

@implementation Originator

- (Memento *)createMemento {
    Memento *memo = [[Memento alloc] init];
    memo.age = self.age;
    memo.name = self.name;
    return memo;
}

- (void)setMemento:(Memento *)memento {
    self.age = memento.age;
    self.name = memento.name;
}

- (NSString *)description {
    return [NSString stringWithFormat:@"name = %@, age = %ld", self.name, self.age];
}

@end

複製代碼

定義備忘錄類Memento

@interface Memento : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

@end
複製代碼

定義看管者類Caretaker

@interface Caretaker : NSObject

- (void)archiveMemento:(Memento *)memento;
- (Memento *)getMemento;

@end

@interface Caretaker ()

@property (nonatomic, strong) Memento *memento;

@end

@implementation Caretaker

- (void)archiveMemento:(Memento *)memento {
    self.memento = memento;
}

- (Memento *)getMemento {
    return self.memento;
}

@end

複製代碼

控制器實現

@implementation ViewController

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Originator *oriAtor = [[Originator alloc] init];
    oriAtor.name = @"sansa";
    oriAtor.age = 18;
    NSLog(@"1 --- %@", oriAtor);
    
    // 保存十八歲的樣子
    Caretaker *taker = [[Caretaker alloc] init];
    [taker archiveMemento:[oriAtor createMemento]];
    
    // 過了好多年
    oriAtor.age = 78;
    NSLog(@"2 --- %@", oriAtor);
    
    // 吃了藥劑,重返十八歲
    [oriAtor setMemento:[taker getMemento]];
    NSLog(@"3 --- %@", oriAtor);
}

@end

/* MementoPattern[38016:1796366] 1 --- name = sansa, age = 18
** MementoPattern[38016:1796366] 2 --- name = sansa, age = 78
** MementoPattern[38016:1796366] 3 --- name = sansa, age = 18
*/
複製代碼

5.總結

**Cocoa Touch框架在歸檔、屬性列表序列化和核心數據中採用了備忘錄模式.

寫在最後

百度百科

設計模式是一套被反覆使用的、多數人知曉的、通過分類編目的、代碼設計經驗的總結。使用設計模式是爲了重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的,設計模式使代碼編制真正工程化,設計模式是軟件工程的基石,如同大廈的一塊塊磚石同樣。項目中合理地運用設計模式能夠完美地解決不少問題,每種模式在現實中都有相應的原理來與之對應,每種模式都描述了一個在咱們周圍不斷重複發生的問題,以及該問題的核心解決方案,這也是設計模式能被普遍應用的緣由。

無論是什麼開發都會用到設計模式,學習好設計模式對咱們對編碼架構設計會有一個很好的提高.固然這也是會有至關大對學習成本,學習了理論後更須要去實踐,Talk is cheap. Show me the code.理論和實踐結合這樣纔會有更好的理解.

最後引用喬幫主的話Stay hungry Stay foolish共勉.

gitHub: PatternDemos

相關文章
相關標籤/搜索