Objective-C設計模式解析-裝飾

看圖識模式

每一個早晨出門前都要穿衣打扮,根據參加的場所選擇不一樣的服飾。
好比如今有若干衣服:運動鞋、運動褲、衛衣、襯衫、西服、皮鞋、內衣等。
提出需求: 這周分別參加公益酒會、運動會、cosplay三個活動。怎麼搭配這些衣服了,設計成類如何實現?編程

能夠這樣搭配,如圖性能

clipboard.png

繼承方式

生成3個子類分別繼承Person類:學習

  • 第一個子類styleOne,披風+紅內衣
  • 第二個子類styleTwo,衛衣+運動鞋+短褲
  • 第三個子類styleThree,西服+皮鞋+領帶

看起來很好,並且解決了問題。可是發現一樣也有一些問題,ui

  • 這些子類都是靜態的、不可改變的,好比天冷了我須要加一件棉服怎麼辦?每一個子類都要改變
  • 一年四季咱們穿的衣服變幻無窮,這樣須要建立多少個子類呢?確定是一個很恐怖的數量

一個子類

在一個子類裏定義所有的功能,須要穿哪件衣服(哪一個功能)就穿哪件衣服(調用對應功能),可是這不知足單一職責原則(不一樣的功能不該該放在同一個類裏面),並且這個類會過於臃腫而沒法維護,而且大部分功能是使用不到的,只有在相應的場景纔會須要。
並且若是增長新功能也要改變這裏類,也不符合開放-封閉原則。atom

分析&解決

經過上面的例子咱們能夠發現2個規律spa

  • 第一: 穿的衣服是能夠任意組合的,理論上穿幾件、穿哪一種類型均可以。設計

    也就是動態添加
  • 第二: 穿完一件衣服能夠再穿另外一件衣服,穿完一件衣服後我仍是我(類型沒發生變化)。穿一件衣服以前不用關心我穿沒穿衣服、穿了幾件衣服,穿完以後一樣也是。調試

    添加以後類型不發生變化,添加先後均可以被一致對待。也就是裝飾前和裝飾後
    沒有什麼不一樣

爲了實現這些目的,能夠這樣設計:code

首先,聲明一個抽象接口Person,它有一個show方法來展現當前的穿着打扮。具體的人(Person)實現這個接口好比黃種人(YellowMan),show方法只輸出人名,在未裝飾以前就只是一個單純的人。
而後再定義一個裝飾類Decorator,它也實現接口Person,但不一樣的是它擁有一個具體對象(YellowMan)的引用,並且多了一個addBehavior方法,這個方法裏實現對具體對象的裝飾(添加職責)。
最後建立具體的Decorator類,實現具體的addBehavior方法。
把一個具體的人類(Person)傳遞建立一個具體的裝飾類,因爲裝飾類(Decorator)和人類(Person)擁有相同的接口,因此它倆的對外使用是一致的。當調用show的時候,經過對具體人類(Person)的引用調用它對應的show方法,同時調用裝飾方法(addBehavior),達到了添加職責的目的。對象

看一下設計類圖:

clipboard.png

這樣咱們就能夠把任意的裝飾類鏈接起來使用,用圖表示應該是這樣的

clipboard.png

有幾件衣服(職責),就建立幾個裝飾類,具體怎麼穿就能夠隨意搭配了。下面看一下代碼怎麼寫?

代碼示例

抽象接口Person

@protocol Person <NSObject>

- (void)show;

@end

具體人YellowMan實現這個接口Person

#import "Person.h"

@interface YellowMan : NSObject <Person>

- (instancetype)initWithName:(NSString *)name;
- (void)show;

@end

@interface YellowMan ()

@property (nonatomic, copy) NSString *name;

@end

@implementation YellowMan 

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

- (void)show
{
    NSLog(@"我是: %@", self.name);
}

@end

定義一個裝飾類Decorator

@interface Decorator : NSObject <Person>

- (instancetype)initWithPerson:(id <Person>)person;
- (void)show;

@end

 @interface Decorator ()

@property (nonatomic, strong) id <Person>person;

@end

@implementation Decorator

- (instancetype)initWithPerson:(id <Person>)person
{
    self = [super init];
    if (self) {
        _person = person;
    }
    return self;
}

- (void)show
{
    [self.person show];
}

@end

定義具體的裝飾類: 襯衫ShirtDecorator

@interface ShirtDecorator : Decorator

@end

@implementation ShirtDecorator

- (void)show
{
    [super show];
    [self addBehavior];
}

- (void)addBehavior
{
    NSLog(@"-- 穿襯衫");
}

@end

定義具體的裝飾類: 西裝SuitDecorator

@interface SuitDecorator : Decorator

@end

@implementation SuitDecorator

- (void)show
{
    [super show];
    [self addBehavior];
}

- (void)addBehavior
{
    NSLog(@"-- 穿西裝");
}

@end

其它的裝飾類都相似,再也不一一寫了;

client調用

YellowMan *aMan = [[YellowMan alloc] initWithName:@"小明"];
    ShirtDecorator *shirtA = [[ShirtDecorator alloc] initWithPerson:aMan];
    SuitDecorator *suitA = [[SuitDecorator alloc] initWithPerson:shirtA];
    [suitA show];
    
    // 小李是超人,內衣穿外面
    YellowMan *bMan = [[YellowMan alloc] initWithName:@"小李"];
    ShirtDecorator *shirtB = [[ShirtDecorator alloc] initWithPerson:bMan];
    SuitDecorator *suitB = [[SuitDecorator alloc] initWithPerson:shirtB];
    UnderwearDecorator *underwear = [[UnderwearDecorator alloc] initWithPerson:suitB];
    [underwear show];

運行結果:

clipboard.png

咱們發現被裝飾過的對象任然和沒裝飾前的同樣,它的功能沒有發生改變,只是多了被裝飾的功能,使用方式也沒有發生變化。
並且被裝飾後的對象還能夠被繼續裝飾,裝飾多少次和裝飾順序徹底能夠動態控制。

模式定義

定義

裝飾模式: 動態地給一個對象添加一些額外的職責。就拓展功能來講,裝飾模式相比生成子類更爲靈活。

結構圖

clipboard.png

裝飾模式包含以下角色:

  • 抽象組件角色(Component):定義一個對象接口,以規範準備接受附加責任的對象,便可以給這些對象動態地添加職責
  • ConcreteComponent: 具體構件,也能夠給這個對象添加一些職責
  • Decorator: 抽象裝飾類,繼承了Component,維持一個指向構件Component對象的實例,並定義一個與抽象組件角色Component接口一致的接口
  • ConcreteDecorator: 具體裝飾類,起到給Component添加職責的功能。

經過上面的結構咱們發現:

  • 裝飾者和被裝飾對象有相同的超類型。
  • 你能夠用一個或多個裝飾者包裝一個對象。
  • 既然裝飾者和被裝飾對象有相同的超類型,因此在任何須要原始對象(被包裝的)的場合 ,能夠用裝飾過的對象代替它。
  • 裝飾者能夠在所委託被裝飾者的行爲以前與/或以後,加上本身的行爲,以達到特定的目的。
  • 對象能夠在任什麼時候候被裝飾,因此能夠在運行時動態地、不限量地用你喜歡的裝飾者來裝飾對象。

類應該對擴展開放,對修改關閉

特徵

目的

擴展對象的功能

裝飾模式能夠提供比繼承更多的靈活性。裝飾模式容許系統動態決定「貼上」一個須要的「裝飾」,或者除掉一個不須要的「裝飾」。繼承關係則不一樣,繼承關係是靜態的,它在系統運行前就決定了。

使用場景

在如下狀況下可使用裝飾模式:

  • 在不影響其餘對象的狀況下,以動態、透明的方式給單個對象添加職責。
  • 須要動態地給一個對象增長功能,這些功能也能夠動態地被撤銷。
  • 當不能採用繼承的方式對系統進行擴充或者採用繼承不利於系統擴展和維護時。不能採用繼承的狀況主要有兩類:第一類是系統中存在大量獨立的擴展,爲支持每一種組合將產生大量的子類,使得子類數目呈爆炸性增加;第二類是由於類定義不能繼承(如final類,object-c中不會有這種狀況)

透明性

裝飾模式可分爲透明裝飾模式和半透明裝飾模式:在透明裝飾模式中,要求客戶端徹底針對抽象編程,裝飾模式的透明性要求客戶端程序不該該聲明具體構件類型和具體裝飾類型,而應該所有聲明爲抽象構件類型;
例如:

// 應該這樣
    id <Person>person = [[YellowMan alloc] initWithName:@"小明"];
    id <Person>shirtPerson = [[ShirtDecorator alloc] initWithPerson:person];
    id <Person>suitPerson = [[SuitDecorator alloc] initWithPerson:shirtPerson];
    [suitPerson show];
    
    // 不該該這樣
    YellowMan *aMan = [[YellowMan alloc] initWithName:@"小明"];
    ShirtDecorator *shirtA = [[ShirtDecorator alloc] initWithPerson:aMan];
    SuitDecorator *suitA = [[SuitDecorator alloc] initWithPerson:shirtA];
    [suitA show];

半透明裝飾模式允 許用戶在客戶端聲明具體裝飾者類型的對象,調用在具體裝飾者中新增的方法。

然而,純粹的裝飾模式很難找到。裝飾模式的用意是在不改變接口的前提下,加強所考慮的類的性能。在加強性能的時候,每每須要創建新的公開的方法。這就致使了大多數的裝飾模式的實現都是「半透明」的,而不是徹底透明的。換言之,容許裝飾模式改變接口,增長新的方法。這意味着客戶端能夠聲明ConcreteDecorator類型的變量,從而能夠調用ConcreteDecorator類中才有的方法。

優缺點

優勢

  • 裝飾模式與繼承關係的目的都是要擴展對象的功能,可是裝飾模式能夠提供比繼承更多的靈活性。
  • 能夠經過一種動態的方式來擴展一個對象的功能,經過配置文件能夠在運行時選擇不一樣的裝飾器,從而實現不一樣的行爲。
  • 經過使用不一樣的具體裝飾類以及這些裝飾類的排列組合,能夠創造出不少不一樣行爲的組合。可使用多個具體裝飾類來裝飾同一對象,獲得功能更爲強大的對象。
  • 具體構件類與具體裝飾類能夠獨立變化,用戶能夠根據須要增長新的具體構件類和具體裝飾類,在使用時再對其進行組合,原有代碼無須改變,符合「開閉原則」

缺點

  • 這種比繼承更加靈活機動的特性,也同時意味着裝飾模式比繼承更加易於出錯,排錯也很困難,對於屢次裝飾的對象,調試時尋找錯誤可能須要逐級排查,較爲煩瑣。
  • 產生不少具體裝飾類。這些裝飾類和它們之間相互鏈接的方式將增長系統的複雜度,加大學習與理解的難度。

Objective-C中的應用

根據Objective-C的特性,有兩種實現方式:

  • 經過真正的子類實現裝飾,上面通用的結構
  • 經過分類Category實現裝飾

第二種方式是使用了Objective-C的語言功能,經過分類向類添加行爲,沒必要進行子類化,這並不是標準的裝飾模式結構,可是實現了裝飾模式一樣的需求。儘管使用分類來實現裝飾模式跟原始風格有偏離,可是實現少許的裝飾器的時候,它比真正子類方式更加輕量、更加容易。

相關文章
相關標籤/搜索