iOS 設(shè)計模式系列二:裝飾者模式

引言

在介紹裝飾者模式之前菠劝,我們先了解一個設(shè)計原則:

多用組合赛糟,少用繼承埃叭。

在平時寫代碼時摸恍,我們應(yīng)該減少類繼承的使用,過多地使用類的繼承會導(dǎo)致類數(shù)目過于龐大而變得難以維護(hù)赤屋,而使用組合可以讓我們的系統(tǒng)更具彈性立镶,更加容易修改和擴(kuò)展。而的接下來將要討論的裝飾者模式正是使用了對象組合的方式益缎,可以讓我們在不修改原有代碼的前提下谜慌,動態(tài)地給對象賦予新的職責(zé)然想。

需求場景

這里借用 《Head First 設(shè)計模式》里的一個例子莺奔。去星巴克買過咖啡的同學(xué)都知道那里有很多種類的飲料,如果我們要為星巴克開發(fā)一個結(jié)賬買單的系統(tǒng)变泄,那么應(yīng)該怎么設(shè)計呢令哟?

需求分析和解決方案

首先進(jìn)行需求分析,星巴克里有種類繁多的咖啡妨蛹,結(jié)賬時我們需要知道咖啡的名稱和價格屏富。所以,我們很容易想到抽出一個咖啡基類蛙卤,然后繼承實(shí)現(xiàn)各個種類的咖啡狠半。但是,這樣做的問題是颤难,星巴克里有幾十種飲料神年,如果我們給每種咖啡都創(chuàng)建一個類,會導(dǎo)致類數(shù)目過于龐大行嗤,而且星巴克也在不斷地推出新的品種已日,這會給我們系統(tǒng)的維護(hù)帶來不小的麻煩。

接下來我們進(jìn)一步分析栅屏,造成咖啡飲料價格不同的原因在于:

  1. 咖啡本身的種類不同(比如飘千,咖啡可以分為濃縮咖啡(Espresso)、無咖啡因咖啡(Decaf)栈雳、深度烘焙咖啡(DarkRoast)等)护奈。
  2. 咖啡里加的調(diào)料(牛奶、豆?jié){哥纫、抹茶霉旗、摩卡等)不同。
    所以,我們可以將問題簡化成:

咖啡飲料的價格 = 咖啡本身的價格 + 各種調(diào)料的價格

比如奖慌,Espresso Macchiato(濃縮瑪奇朵)的價格 = Espresso(濃縮咖啡) 的價格 +Milk(牛奶)的價格 + Mocha(摩卡)的價格抛虫。

我們看到,Milk 就像一個“裝飾者(Decorator)”简僧,而 Espresso 就像一個“被裝飾者(Component)”建椰,可以在“被裝飾者”上添加各種“裝飾者”來制作出全新口味的 Espresso 咖啡,當(dāng)然也可以把“裝飾者”放在其它類別的“被裝飾者”上岛马。只要對“被裝飾者”和“裝飾品”進(jìn)行組合棉姐,我們就能制作出各種各樣的咖啡。

需要格外強(qiáng)調(diào)的是啦逆,這里的組合要求是動態(tài)地進(jìn)行組合伞矩,即裝飾者與被裝飾者是在運(yùn)行的時候綁定,而不是寫死在類里(比如繼承夏志,類繼承是在編譯的時候增加行為乃坤,而裝飾者模式是在運(yùn)行時增加行為)。在接下來裝飾者模式的實(shí)現(xiàn)過程中沟蔑,很多實(shí)現(xiàn)細(xì)節(jié)都是為了達(dá)到這個目標(biāo)湿诊。

綜合以上的分析,我們給出如下的設(shè)計結(jié)構(gòu):


分析完整個結(jié)構(gòu)瘦材,我們再來看一下厅须,當(dāng)顧客點(diǎn)一杯咖啡時,這些類之間應(yīng)該如何互相協(xié)作調(diào)用食棕。假設(shè)顧客點(diǎn)了一杯 Espresso Macchiato(濃縮瑪奇朵)朗和,那么系統(tǒng)將會開始以下的工作流程:

  1. 首先實(shí)例化一個被裝飾者 Espresso 對象,對象里包含咖啡的基本價格和名稱簿晓。
  2. 實(shí)例化一個裝飾者 Milk 對象眶拉,對象里包含 milk 的價格和名稱,同時讓 Milk 對象持有 Espresso 對象抢蚀。
  3. 接下來調(diào)用 Milk 對象的 cost() 方法镀层,這個方法會去調(diào)用 Espressocost() 方法,并將返回的價格和 milk 的價格相加皿曲,這樣我們就可以得到 Espresso 配 milk 的價格唱逢。
  4. 實(shí)例化一個裝飾者 Mocha 對象,對象里包含 mocha 的價格和名稱屋休,同時讓 Mocha 對象持有上述 Milk 對象坞古。
  5. 最后調(diào)用 Mocha 對象的 cost() 方法,這個方法會去調(diào)用 Milk 對象的 cost() 方法劫樟,并將返回的價格和 mocha 的價格相加痪枫,如此我們就得到了 Espresso 配 milk 和 mocha 的價格织堂。

這樣一層一層地嵌套調(diào)用是不是很像俄羅斯套娃呢?

代碼實(shí)現(xiàn)

下面我們給出詳細(xì)的代碼實(shí)現(xiàn):

Beverage 協(xié)議

Beverage.h

    #import <Foundation/Foundation.h>
    
    @protocol Beverage <NSObject>
    
    @optional
    - (NSString *)getName;
    
    - (double)cost;
    
    @end

Espresso 類

Espresso.h

    #import <Foundation/Foundation.h>
    #import "Beverage.h"
    
    @interface Espresso : NSObject<Beverage>
    
    @end

Espresso.m

    #import "Espresso.h"
    
    @implementation Espresso{
        NSString *_name;
    }
    
    - (instancetype)init{
        
        if (self = [super init]) {
            _name = @"Espresso";
        }
        return self;
    }
    
    - (NSString *)getName{
        return _name;
    }
    
    - (double)cost{
        return 1.99;
    }
    
    @end

CondimentDecorator 協(xié)議

CondimentDecorator.h

    #import <Foundation/Foundation.h>
    #import "Beverage.h"
    
    @protocol CondimentDecorator <Beverage>
    
    @end

Milk 類

Milk.h

    #import <Foundation/Foundation.h>
    #import "Beverage.h"
    #import "CondimentDecorator.h"
    
    @interface Milk : NSObject <CondimentDecorator>
    
    @property (strong, nonatomic)id<Beverage> beverage;
    
    - (instancetype)initWithBeverage:(id<Beverage>) beverage;
    
    @end

Milk.m

    #import "Milk.h"
    
    @implementation Milk{
        NSString *_name;
    }
    
    - (instancetype)initWithBeverage:(id<Beverage>)beverage{
        if (self = [super init]) {
            _name = @"Milk";
            self.beverage = beverage;
        }
        return self;
    }
    
    - (NSString *)getName{
        return [NSString stringWithFormat:@"%@ + %@", [self.beverage getName], _name ];
    }
    
    - (double)cost{
        return .30 + [self.beverage cost];
    }
    
    @end

Mocha 類

Mocha.h

    #import <Foundation/Foundation.h>
    #import "Beverage.h"
    #import "CondimentDecorator.h"
    
    @interface Mocha : NSObject<CondimentDecorator>
    
    @property (strong, nonatomic)id<Beverage> beverage;
    
    - (instancetype)initWithBeverage:(id<Beverage>) beverage;
    @end

Mocha.m

    #import "Mocha.h"
    
    @implementation Mocha{
        NSString *_name;
    }
    
    - (instancetype)initWithBeverage:(id<Beverage>)beverage{
        if (self = [super init]) {
            self.beverage = beverage;
            _name = @"Mocha";
        }
        return self;
    }
    
    - (NSString *)getName{
        return [NSString stringWithFormat:@"%@ + %@", [self.beverage getName], _name];
    }
    
    - (double)cost{
        return .20 + [self.beverage cost];
    }
    
    @end

整合調(diào)用

main.m

    #import <Foundation/Foundation.h>
    #import "Espresso.h"
    #import "DarkRoast.h"
    #import "Milk.h"
    #import "Mocha.h"
    #import "Soy.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
           
            id<Beverage> espresso = [[Espresso alloc]init];
            NSLog(@"name: %@ \n cost: %f \n", [espresso getName], [espresso cost]);
            
            espresso = [[Milk alloc]initWithBeverage:espresso];
            espresso = [[Mocha alloc]initWithBeverage:espresso];
            NSLog(@"name: %@ \n cost:%f", [espresso getName], [espresso cost]);
        }
        return 0;
    }

總結(jié)

到這里我們已經(jīng)對裝飾者模式有了一個比較全面的了解奶陈,最后來概括一下什么是裝飾者模式:

裝飾者模式易阳,是面向?qū)ο缶幊填I(lǐng)域中,一種動態(tài)地往一個類中添加新的行為的設(shè)計模式吃粒。就功能而言潦俺,修飾模式相比生成子類更為靈活,這樣可以給某個對象而不是整個類添加一些功能徐勃∈率荆——《設(shè)計模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》

完整的源代碼地址

Github地址: https://github.com/Zentopia/DesignPatterns

參考資料

  • 《Head First 設(shè)計模式(Java)》
  • 《設(shè)計模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市僻肖,隨后出現(xiàn)的幾起案子肖爵,更是在濱河造成了極大的恐慌,老刑警劉巖臀脏,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件劝堪,死亡現(xiàn)場離奇詭異,居然都是意外死亡谁榜,警方通過查閱死者的電腦和手機(jī)幅聘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進(jìn)店門凡纳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窃植,“玉大人,你說我怎么就攤上這事荐糜∠锪” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵暴氏,是天一觀的道長延塑。 經(jīng)常有香客問我,道長答渔,這世上最難降的妖魔是什么关带? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮沼撕,結(jié)果婚禮上宋雏,老公的妹妹穿的比我還像新娘。我一直安慰自己务豺,他們只是感情好磨总,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著笼沥,像睡著了一般蚪燕。 火紅的嫁衣襯著肌膚如雪娶牌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天馆纳,我揣著相機(jī)與錄音诗良,去河邊找鬼。 笑死鲁驶,一個胖子當(dāng)著我的面吹牛累榜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播灵嫌,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼壹罚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了寿羞?” 一聲冷哼從身側(cè)響起猖凛,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绪穆,沒想到半個月后辨泳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡玖院,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年菠红,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片难菌。...
    茶點(diǎn)故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡试溯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出郊酒,到底是詐尸還是另有隱情遇绞,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布燎窘,位于F島的核電站摹闽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏褐健。R本人自食惡果不足惜付鹿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蚜迅。 院中可真熱鬧舵匾,春花似錦、人聲如沸慢叨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拍谐。三九已至烛缔,卻和暖如春馏段,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背践瓷。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工院喜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晕翠。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓喷舀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親淋肾。 傳聞我的和親對象是個殘疾皇子硫麻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容