設計模式-裝飾者模式

一修Grace發表於2019-03-05

要解決的問題

考慮一個咖啡店收費的問題:如何實現靈活的咖啡的價格計算。 咖啡店主要賣咖啡,但是後來為了滿足不同客戶的不同口味,只是純咖啡,顯得太單調了,就考慮增加不同的搭配,搭配不同的配料後會組成另一種飲品,這樣,品種豐富了,但隨之問題也來了,如何為不同新的品種計算新的準確的價格。兩種方案:

第一種:可以採用繼承的方式,將純咖啡作為基類,而後需要什麼品種的話,可以生成一個子類,單獨作為一個品種來重寫計算價格的方法,並且還可以為這個品種新增其他功能。但是:繼承有一個很大的問題就是,這樣的方案實現是首先你是知道都有什麼品種的,才會派生出各種子類,但是,如果後續想要在某個現有品種中去掉一些或者加上一些內容,甚至直接刪除這個品種,就會很麻煩的總是修改對應的類了,而且還有個缺點是:會產生很多種子類,如果品種很多,而且每個品種的差別很小的時候,都分別單獨的作為一個類就會很麻煩。

第二種:就是採用我們今天要講的這篇文章的主題裝飾者模式。先類比一下生活中的一個例子:一張紙質的照片,想要讓這張照片儲存的久一點,我們可以先給這張照片塑封;塑封后,覺得還不夠的話,可能還會給這張照片裝一個相框;加一個相框還覺得不能好好保護相片的話,再加個玻璃罩。在這個例子中,我們可以理解照片本身就是要被裝飾的物件,塑封膠、相框、玻璃罩都是作為裝飾者。每一層的裝飾者都不會修改最裡邊的被裝飾的物件。這裡我們可以把具體的咖啡飲品當做被裝飾者,要加入的食物或飲料當做裝飾者,每一種咖啡飲品可以被不同的裝飾者裝飾。

將上述為不同咖啡飲品計算價格的問題用程式設計的概念來講就是如何能夠透明地給一個物件增加功能,並實現功能的動態組合。這就是裝飾者模式的功能。

模式定義

裝飾者模式能夠實現動態地為物件新增功能,從一個物件外部透明的給物件增加功能。透明地給一個物件增加功能,就是說要給一個物件增加功能,但是不能讓這個物件知道,也就是不能去修改這個物件

每個被裝飾者可以被多個裝飾者裝飾。例如:黑咖啡(被裝飾者)可以被牛奶(裝飾者)、水果(裝飾者)裝飾,而且,不同的裝飾者之間沒有先後順序的限制。

具體實現

裝飾者需要和被裝飾的物件繼承於同樣的類或者實現同樣的介面(iOS中稱遵守同樣的協議),而後,在具體的裝飾者的實現中,轉調被裝飾者物件【這就需要裝飾者物件持有一個被裝飾者物件】

下邊是具體實現的UML圖和不同類之間的呼叫層次圖。

設計模式-裝飾者模式

  • CoffeeComponent:咖啡基類(也可以是介面/協議)
  • BlackCoffee:具體的咖啡,就是要被裝飾的物件。
  • CondimentDecorator:配料的基類(裝飾者的基類),而且需要繼承於被裝飾者基類CoffeeComponent,同時還要持有一個CoffeeComponent型別的屬性。
  • MilkDecorator:牛奶裝飾者,具體的裝飾者物件。

設計模式-裝飾者模式

從上邊的層次圖中可以看出,多層裝飾者一層層的包在被裝飾物件的外部,功能方法的呼叫也是一層層遞迴呼叫被裝飾的物件。從圖中可以看出:當黑咖啡被牛奶裝飾後,牛奶裝飾器就成為了新的被裝飾者,可以被後續的其它裝飾者裝飾,而且各個裝飾者之間是沒有順序要求的。順序完全可以按照自己的意願來進行。

//********************************咖啡元件(基類)*********************
@interface CoffeeComponent : NSObject
- (double)getPrice;
@end

@implementation CoffeeComponent//抽象元件,可以寫預設實現的方法,也可以用協議實現
- (double)getPrice
{
    return 0.f;
}
@end

//********************************黑咖啡(具體咖啡類)*********************
@interface BlackCoffee : CoffeeComponent//繼承於抽象元件的具體元件
- (double)getPrice;
@end

@implementation BlackCoffee
- (double)getPrice
{
    return 5;
}
@end

//********************************裝飾者基類*********************
@interface CondimentDecorator : CoffeeComponent//繼承於元件的裝飾者抽象類
- (instancetype)initWithComponent:(CoffeeComponent *)component;
@property (nonatomic,strong)CoffeeComponent *component;
@end

@implementation CondimentDecorator
- (instancetype)initWithComponent:(CoffeeComponent *)component
{
    if (self = [super init]) {
        _component = component;
    }
    return self;
}
@end
//********************************牛奶裝飾者(具體裝飾者)*********************
@interface MilkDecorator : CondimentDecorator//繼承於抽象佐料裝飾者的具體裝飾者
@end

@implementation MilkDecorator
- (double)getPrice
{
    NSLog(@"牛奶加了2元");
    return 2 + [self.component getPrice];
}
@end
//在此省略其它裝飾者的程式碼,與牛奶裝飾者的程式碼一樣。程式碼可以檢視demo。
//=========================外部呼叫=====================
    //純咖啡
    BlackCoffee *blackCoffee = [[BlackCoffee alloc]init];
    //加奶
    MilkDecorator *milkDecorator = [[MilkDecorator alloc]initWithComponent:blackCoffee];
    //加豆漿
    SoyDecorator *soyDecorator = [[SoyDecorator alloc]initWithComponent:milkDecorator];
    //加水果
    FruitDecorator *fruitDecorator = [[FruitDecorator alloc]initWithComponent:soyDecorator];
    NSLog(@"一共多少錢%f",[fruitDecorator getPrice]);

複製程式碼

總結

  • 裝飾者模式比繼承更靈活:繼承是靜態的,而且繼承的子類都有基類同樣的功能,但是裝飾者模式能夠把功能分離到不同的裝飾器中,可以動態的選擇想要什麼功能。
  • 裝飾者模式的本質是動態組合:動態的進行裝飾器的組合,可以為被裝飾物件透明的增加功能。
  • 裝飾者模式不僅可以增加功能,也可以完全實現新的功能和控制功能的訪問。可以在裝飾器中呼叫被裝飾物件功能的時候,進行控制。
  • 裝飾者模式的缺點:會產生細粒度的物件,如果一系列的複雜功能,想要把不同的功能都細分到不同的裝飾器上,就會產生很多細粒度的物件。

以上作為筆者自己的讀書筆記,如有理解錯誤的地方,還請指出。謝謝!

Demo地址


參考致謝:

《研磨設計模式》

《Head First設計模式》

相關文章