引言
在介紹裝飾者模式之前菠劝,我們先了解一個設(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)一步分析栅屏,造成咖啡飲料價格不同的原因在于:
- 咖啡本身的種類不同(比如飘千,咖啡可以分為濃縮咖啡(Espresso)、無咖啡因咖啡(Decaf)栈雳、深度烘焙咖啡(DarkRoast)等)护奈。
- 咖啡里加的調(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)將會開始以下的工作流程:
- 首先實(shí)例化一個被裝飾者
Espresso
對象,對象里包含咖啡的基本價格和名稱簿晓。 - 實(shí)例化一個裝飾者
Milk
對象眶拉,對象里包含 milk 的價格和名稱,同時讓Milk
對象持有Espresso
對象抢蚀。 - 接下來調(diào)用
Milk
對象的cost()
方法镀层,這個方法會去調(diào)用Espresso
的cost()
方法,并將返回的價格和 milk 的價格相加皿曲,這樣我們就可以得到 Espresso 配 milk 的價格唱逢。 - 實(shí)例化一個裝飾者
Mocha
對象,對象里包含 mocha 的價格和名稱屋休,同時讓Mocha
對象持有上述Milk
對象坞古。 - 最后調(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ǔ)》