
大部分公司都有銷售團(tuán)隊(duì),假設(shè)老板給你布置了一個(gè)任務(wù),讓你按照下面的要求開發(fā)一套程序來計(jì)算銷售團(tuán)隊(duì)每個(gè)月的工資。
- 每個(gè)人當(dāng)月業(yè)務(wù)獎金 = 當(dāng)月銷售額 * 3%
- 每個(gè)人的累積獎金 = 總的回款額 * 0.1%
- 銷售經(jīng)理的團(tuán)隊(duì)獎金 = 團(tuán)隊(duì)總銷售額 * 1%
每個(gè)人的工資就是基本工資加上獎金,那么按照常規(guī)模式我們來看下如何讓實(shí)現(xiàn)。
#import "calculateBonus.h"
@implementation calculateBonus
-(NSInteger)calculateSalary:(NSInteger)monthSales sumSales:(NSInteger)sumSales isManager:(BOOL)manager{
//基本工資都是8000
NSInteger salary = 8000;
salary += [self monthBonus:monthSales];
salary += [self sumBonus:sumSales];
if (manager) {
salary += [self groupBonus];
}
return salary;
}
//當(dāng)月獎金
-(NSInteger)monthBonus:(NSInteger)monthSales{
return monthSales * 0.003;
}
//累積獎金
-(NSInteger)sumBonus:(NSInteger)sumSales{
return sumSales * 0.001;
}
//團(tuán)隊(duì)獎金
-(NSInteger)groupBonus{
//簡單起見,團(tuán)隊(duì)的銷售總額設(shè)置為100000
return 100000 * 0.01;
}
@end
測試下:
calculateBonus *calculate = [calculateBonus new];
NSInteger salary1 = [calculate calculateSalary:12222 sumSales:12000 isManager:YES];
NSLog(@"經(jīng)理工資:%zd", salary1);
NSInteger salary2 = [calculate calculateSalary:21333 sumSales:23111 isManager:NO];
NSLog(@"員工甲:%zd", salary2);
NSInteger salary3 = [calculate calculateSalary:22113 sumSales:11222 isManager:NO];
NSLog(@"員工乙:%zd", salary3);
輸出:
2016-12-14 08:57:58.600 裝飾者模式[64313:1880733] 經(jīng)理工資:9048
2016-12-14 08:57:58.601 裝飾者模式[64313:1880733] 員工甲:8086
2016-12-14 08:57:58.601 裝飾者模式[64313:1880733] 員工乙:8077
Program ended with exit code: 0
看起來運(yùn)行良好,好,該來的還是來了,該需求。
現(xiàn)在要增加一個(gè)環(huán)比獎金:就是本月銷售額比上月又增加,然后達(dá)到一定比例,就會有獎金,增加越多,獎金比率越高。你說這還不簡單,再加一個(gè)環(huán)比獎金計(jì)算方法不就是了。那么如果工資計(jì)算的方式也換了呢?不同級別的人員或者員工的計(jì)算獎金的方式也換了呢?
假設(shè)
甲的總工資 = 基本工資 + 當(dāng)月銷售獎金 + 環(huán)比獎金
乙的工資 = 基本工資 + 環(huán)比獎金
丙的工資 = 基本工資 + 當(dāng)月銷售獎金
丁的工資 = 基本工資 + 環(huán)比獎金 + 團(tuán)隊(duì)獎金
后面再給你制定十幾種不同的計(jì)算方式,崩潰了沒有?按照上面的寫法,那么每一種總工資計(jì)算方式你都要寫一種方法,再假設(shè)這些人的總工資計(jì)算方式每個(gè)季度還會有調(diào)整,你怎么辦,接著改?
仔細(xì)分析下上面的需求,總工資的計(jì)算有兩部分:基本工資加上各種獎金,基本工資是固定的,麻煩的地方就在于獎金的計(jì)算方式是動態(tài)變化的,有各種各樣的組合方式。按照設(shè)計(jì)模式的思想:封裝變化,這里變化的部分是獎金的組合方式,那么我們就把這部分封裝起來。
我們可以采取這樣的方式,把每張獎金的計(jì)算方式都單獨(dú)成類,然后需要用到哪種獎金計(jì)算,就把這個(gè)獎金計(jì)算和基本工資組合起來,需要多少種獎金計(jì)算方式,就組合多少種。這樣實(shí)現(xiàn)起來,是不是非常靈活?以后你想修改或者增加減少獎金計(jì)算方式,只需要修改或者增加減少一個(gè)獎金計(jì)算方式就可以了,至于每個(gè)人的總工資計(jì)算方式各不相同,就更簡單了,交給客戶端自由組合。
總結(jié)起來就是如下三個(gè)要求:
獎金計(jì)算方式要靈活,可以動態(tài)增加或者減少
可以動態(tài)組合獎金計(jì)算方式
要實(shí)現(xiàn)上面的功能,就要清楚我們今天的豬腳:裝飾器模式。
下面來認(rèn)識下這位仁兄
定義
動態(tài)地給一個(gè)對象添加一些額外的職責(zé)。就增加功能來說, 裝飾模式比生成子類更為靈活。
一般我們在給一個(gè)原本的類添加功能的時(shí)候,都會想到使用繼承,在原有的類上擴(kuò)展新的功能,但是繼承有一個(gè)非常大的缺點(diǎn):和父類耦合性太高。如果后續(xù)需要添加或者減少功能,就不得不每次都要修改子類,而且如果修改了父類,對子類的影響也非常大。
所以我們一般優(yōu)先考慮使用組合來實(shí)現(xiàn)功能的擴(kuò)展,這也是設(shè)計(jì)模式的一個(gè)原則:多用組合,少用繼承。裝飾器模式就是實(shí)現(xiàn)組合功能的一種方式,它可以透明的給原本的類增加或者減少功能,而且可以把多個(gè)功能組合在一起,他不會改變原有類的功能,只是在原來功能的基礎(chǔ)上加上一些新功能,而這些操作被裝飾對象是毫不知情的。
比如上面的計(jì)算總工資,原有的對象是基本工資,但是需要在基本工資的基礎(chǔ)上加上各種獎金,也就是給基本工資擴(kuò)展了功能,但是基本工資這個(gè)原有功能是不會改變的,只是給他加上了各種各樣的獎金,豐富了它的功能,最后算出來的還是工資,也就是保持原有的類型(整數(shù)型)不改變,這點(diǎn)要切記。
UML結(jié)構(gòu)如及說明

實(shí)現(xiàn)裝飾器模式要注意如下幾點(diǎn):
-
接 口 的 一 致 性
裝飾對象的接口必須與它所裝飾的 C o m p o n e n t 的 接 口 是 一 致 的 , 因 此 ,
所有的 concreteDecorator 類必須有一個(gè)公共的父類 -
省略抽象的 D e c o r a t o r 類
當(dāng)你僅需要添加一個(gè)職責(zé)時(shí),沒有必要定義抽象 D e c o r a t o r 類。 你常常需要處理現(xiàn)存的類層次結(jié)構(gòu)而不是設(shè)計(jì)一個(gè)新系統(tǒng),這時(shí)你可以把 D e c o r a t o r 向 C o m p o n e n t 轉(zhuǎn)發(fā)請求的職責(zé)合并到 C o n c r e t e D e c o r a t o r 中。
-
保持 C o m p o n e n t 類 的 簡 單 性
為 了 保 證 接 口 的 一 致 性 , 組 件 和 裝 飾 必 須 有 一 個(gè) 公 共 的 C o m p o n e n t 父類。因此保持這個(gè)類的簡單性是很重要的;即,它應(yīng)集中于定義接口而不是存儲數(shù)據(jù)。對數(shù)據(jù)表示的定義應(yīng)延遲到子類中,否則 C o m p o n e n t 類 會 變 得 過 于 復(fù) 雜 和 龐 大 , 因 而難以大量使用。賦予 C o m p o n e n t太 多 的 功 能 也 使 得 , 具 體 的 子 類 有 一 些 它 們 并 不 需 要 的 功 能的可能性大大增加。
-
改 變 對 象 外 殼 與 改 變 對 象 內(nèi) 核
我們可以將 D e c o r a t o r 看 作 一 個(gè) 對 象 的 外 殼 , 它 可 以 改 變這個(gè)對象的行為。另外一種方法是改變對象的內(nèi)核。例如, S t r a t e g y 模 式 就 是 一 個(gè) 用 于 改變內(nèi)核的很好的模式。
當(dāng) C o m p o n e n t 類 原 本 就 很 龐 大 時(shí) , 使 用 D e c o r a t o r 模 式 代 價(jià) 太 高 , S t r a t e g y模 式 相 對 更 好 一 些。在 S t r a t e g y 模式中,組件將它的一些行為轉(zhuǎn)發(fā)給一個(gè)獨(dú)立的策略對象,我們可以替換s t r a t e g y 對象,從而改變或擴(kuò)充組件的功能。

代碼實(shí)現(xiàn)
1、定義抽象基類
先定義一個(gè)抽象基類,工資類和獎金計(jì)算方式類都繼承自這個(gè)類,該類定義了一個(gè)公開接口,用于計(jì)算獎金
#import <Foundation/Foundation.h>
@interface component : NSObject
-(NSInteger)calculateSalary:(NSInteger)monthSales sumSales:(NSInteger)sumSales;
@end
=================
2、定義工資類(被裝飾對象)
#import "component.h"
@interface concreteComponent : component
@end
======================
//被裝飾對象,基本工資
#import "concreteComponent.h"
@implementation concreteComponent
-(NSInteger)calculateSalary:(NSInteger)monthSales sumSales:(NSInteger)sumSales{
//基本工資8000
return 8000;
}
@end
3、定義抽象裝飾器
定義一個(gè)抽象裝飾器,繼承自抽象基類component,每個(gè)具體的裝飾器繼承自該類,該類主要做一些初始化工作
#import "component.h"
@interface Decorator : component
@property(strong,nonatomic)component *components;
- (instancetype)initWithComponet:(component *)component;
@end
=================
#import "Decorator.h"
@implementation Decorator
- (instancetype)initWithComponet:(component *)component
{
self = [super init];
if (self) {
self.components = component;
}
return self;
}
-(NSInteger)calculateSalary:(NSInteger)monthSales sumSales:(NSInteger)sumSales{
return [self.components calculateSalary:monthSales sumSales:sumSales];
}
@end
4、具體裝飾器
每月銷售獎金裝飾器
#import "Decorator.h"
@interface monthBonusDecorator : Decorator
@end
==================
#import "monthBonusDecorator.h"
@implementation monthBonusDecorator
-(NSInteger)calculateSalary:(NSInteger)monthSales sumSales:(NSInteger)sumSales{
NSInteger salary = [self.components calculateSalary:monthSales sumSales:sumSales];
NSInteger bonus = monthSales * 0.03;
NSLog(@"當(dāng)月銷售獎金:%zd", bonus);
return salary += bonus;
}
@end
累積獎金裝飾器
#import "Decorator.h"
@interface sumBonusDecatorator : Decorator
@end
================
#import "sumBonusDecatorator.h"
@implementation sumBonusDecatorator
-(NSInteger)calculateSalary:(NSInteger)monthSales sumSales:(NSInteger)sumSales{
NSInteger salary = [self.components calculateSalary:monthSales sumSales:sumSales];
NSInteger bonus = sumSales * 0.01;
NSLog(@"累積銷售獎金:%zd", bonus);
return salary += bonus;
}
@end
團(tuán)隊(duì)獎金裝飾器
#import "Decorator.h"
@interface groupBonusDecorator : Decorator
@end
=================
#import "groupBonusDecorator.h"
@implementation groupBonusDecorator
-(NSInteger)calculateSalary:(NSInteger)monthSales sumSales:(NSInteger)sumSales{
NSInteger salary = [self.components calculateSalary:monthSales sumSales:sumSales];
NSInteger bonus = 100000 * 0.01;
NSLog(@"團(tuán)隊(duì)獎金:%zd", bonus);
return salary += bonus;
}
@end
5、測試
//基本工資,被裝飾對象
component *c1 = [concreteComponent new];
//裝飾器
Decorator *d1 = [[monthBonusDecorator alloc]initWithComponet:c1];
Decorator *d2 = [[sumBonusDecatorator alloc]initWithComponet:d1];
NSInteger salary1 = [d2 calculateSalary:10000 sumSales:12212];
NSLog(@"\n獎金組合方式:當(dāng)月銷售獎金 + 累積銷售獎金 \n 總工資 = %zd", salary1);
NSLog(@"\n=============================================================================");
Decorator *d3 = [[monthBonusDecorator alloc]initWithComponet:c1];
Decorator *d4 = [[sumBonusDecatorator alloc]initWithComponet:d3];
Decorator *d5 = [[groupBonusDecorator alloc]initWithComponet:d4];
NSInteger salary2 = [d5 calculateSalary:12100 sumSales:12232];
NSLog(@"\n獎金組合方式:當(dāng)月銷售獎金 + 累積銷售獎金 + 團(tuán)隊(duì)獎金 \n 總工資 = %zd", salary2);
NSLog(@"\n=============================================================================");
Decorator *d6 = [[monthBonusDecorator alloc]initWithComponet:c1];
Decorator *d7 = [[groupBonusDecorator alloc]initWithComponet:d6];
NSInteger salary3 = [d7 calculateSalary:23111 sumSales:231111];
NSLog(@"\n獎金組合方式:當(dāng)月銷售獎金 + 團(tuán)隊(duì)獎金 \n 總工資 = %zd", salary3);
輸出如下
2016-12-14 10:34:31.280 裝飾者模式[64586:1944336] 當(dāng)月銷售獎金:300
2016-12-14 10:34:31.280 裝飾者模式[64586:1944336] 累積銷售獎金:122
2016-12-14 10:34:31.280 裝飾者模式[64586:1944336]
獎金組合方式:當(dāng)月銷售獎金 + 累積銷售獎金
總工資 = 8422
=============================================================================
2016-12-14 10:34:31.280 裝飾者模式[64586:1944336] 當(dāng)月銷售獎金:363
2016-12-14 10:34:31.280 裝飾者模式[64586:1944336] 累積銷售獎金:122
2016-12-14 10:34:31.280 裝飾者模式[64586:1944336] 團(tuán)隊(duì)獎金:1000
2016-12-14 10:34:31.280 裝飾者模式[64586:1944336]
獎金組合方式:當(dāng)月銷售獎金 + 累積銷售獎金 + 團(tuán)隊(duì)獎金
總工資 = 9485
=============================================================================
2016-12-14 10:34:31.281 裝飾者模式[64586:1944336] 當(dāng)月銷售獎金:693
2016-12-14 10:34:31.281 裝飾者模式[64586:1944336] 團(tuán)隊(duì)獎金:1000
2016-12-14 10:34:31.281 裝飾者模式[64586:1944336]
獎金組合方式:當(dāng)月銷售獎金 + 團(tuán)隊(duì)獎金
總工資 = 9693
6、小結(jié)
從上面的測試可以看出,不管是使用何種獎金組合方式,只需要調(diào)用對應(yīng)的裝飾器即可,非常靈活。通過上面的代碼我們看到,裝飾器是一層層包裹的,基本工資被月工資裝飾器包裹,月工資裝飾器被累積獎金裝飾器包裹,累積裝飾器被團(tuán)隊(duì)獎金裝飾器包裹,當(dāng)調(diào)用計(jì)算獎金的公式的時(shí)候,就會按照順序?qū)訉舆f歸調(diào)用每個(gè)裝飾器的功能,到最后算出總工資,我們來用示意圖看看調(diào)用過程。
由于每個(gè)裝飾器之間是完全獨(dú)立的,所以我們可以使用任何我們想要的方式去組合這些裝飾器,比如多次重復(fù)調(diào)用同一個(gè)裝飾器,調(diào)換裝飾器的順序等等。

適用性
在如下情況可以考慮使用對象組合
在不影響其他對象的情況下,以動態(tài)、透明的方式給單個(gè)對象添加職責(zé)。
處理那些可以撤消的職責(zé)。
當(dāng)不能采用生成子類的方法進(jìn)行擴(kuò)充時(shí)
一種情況是,可能有大量獨(dú)立的擴(kuò)展,為支持每一種組合將產(chǎn)生大量的子類,使得子類數(shù)目呈爆炸性增長。另一種情況可能是因?yàn)轭惗x被隱藏,或類定義不能用于生成子類。
優(yōu)缺點(diǎn)
-
比繼 承 更 靈 活
與 對 象 的 靜 態(tài) 繼 承 ( 多 重 繼 承 ) 相 比 , D e c o r a t o r 模 式 提 供 了 更 加
靈活的向?qū)ο筇砑勇氊?zé)的方式??梢杂锰砑雍头蛛x的方法,用裝飾在運(yùn)行時(shí)刻增加和刪除職 責(zé)。相比之下,繼承機(jī)制要求為每個(gè)添加的職責(zé)創(chuàng)建一個(gè)新的子類。這會產(chǎn)生許多新的類,并且會增加系統(tǒng)的復(fù)雜度。此外,為一 個(gè)特定的 C o m p o n e n t 類 提 供 多 個(gè) 不 同 的 D e c o r a t o r 類 , 這 就 使 得 你 可 以 對 一 些 職 責(zé) 進(jìn) 行 混 合 和 匹配。
使用 D e c o r a t o r 模式可以很容易地重復(fù)添加一個(gè)特性。 -
避 免 了高層次類 有 太 多 的 特 征
D e c o r a t o r模 式 提 供 了 一 種 “ 即 用 即 付 ” 的 方 法來添加職責(zé)。它并不試圖在一個(gè)復(fù)雜的可定制的類中支持所有可預(yù)見的特征,相反,你可 以定義一個(gè)簡單的類,并且用 D e c o r a t o r 類 給 它 逐 漸 地 添 加 功 能 。 可 以 從 簡 單 的 部 件 組 合 出 復(fù) 雜的功能。這樣,應(yīng)用程序不必為不需要的特征付出代價(jià)。同時(shí)也更易于不依賴于 D e c o r a t o r 所擴(kuò)展(甚至是不可預(yù)知的擴(kuò)展)的類而獨(dú)立地定義新類型的 D e c o r a t o r 。 擴(kuò) 展 一 個(gè) 復(fù) 雜 類 的 時(shí)候,很可能會暴露與添加的職責(zé)無關(guān)的細(xì)節(jié)。
-
產(chǎn)生 許 多 小 對 象
采用 D e c o r a t o r 模 式 進(jìn) 行 系 統(tǒng) 設(shè) 計(jì) 往 往 會 產(chǎn) 生 許 多 看 上 去 類 似 的 小 對 象 , 這些對象僅僅在他們相互連接的方式上有所不同,而不是它們的類或是它們的屬性值有所不 同。盡管對于那些了解這些系統(tǒng)的人來說,很容易對它們進(jìn)行定制,但是很難學(xué)習(xí)這些系統(tǒng), 排錯也很困難。
裝飾器和AOP
關(guān)于面向切換編程的具體解釋看這里
AOP一般用來實(shí)現(xiàn)如下功能:日志記錄,性能統(tǒng)計(jì),安全控制,事務(wù)處理,異常處理等等。將日志記錄,性能統(tǒng)計(jì),安全控制,事務(wù)處理,異常處理等代碼從業(yè)務(wù)邏輯代碼中劃分出來,通過對這些行為的分離,我們希望可以將它們獨(dú)立到非業(yè)務(wù)邏輯的方法中,進(jìn)而改變這些行為的時(shí)候不影響業(yè)務(wù)邏輯的代碼。
看上面的描述就知道,裝飾器就是一個(gè)很好實(shí)現(xiàn)AOP的方式,因?yàn)檠b飾器就是在不改變原有功能的前提下對其進(jìn)行透明擴(kuò)展的。
我們目前有一個(gè)鴨子類,實(shí)現(xiàn)呱呱叫的一個(gè)方法,現(xiàn)在我希望在不改變原有功能的情況下,統(tǒng)計(jì)鴨子叫了多少次,這就是AOP中的日志記錄功能,我們可以使用裝飾器模式來實(shí)現(xiàn),具體代碼我就不貼了,直接看最后的demo。
裝飾器在iOS中的運(yùn)用
我們應(yīng)該用過一些圖片處理app,可以給圖片加上各種各樣的濾鏡或者裁剪旋轉(zhuǎn)圖片等等功能,其實(shí)這些也可以使用裝飾器來實(shí)現(xiàn),可以把每個(gè)功能都實(shí)現(xiàn)為一個(gè)裝飾器,然后用戶選擇使用什么功能,就給圖片加上對應(yīng)的裝飾器去做處理,這樣做是不是非常靈活?
其實(shí)在iOS里面已經(jīng)為我們提供了類似裝飾器模式的功能的方法:category。category也可以透明的為一個(gè)類添加方法,下面我就使用一個(gè)小demo來演示如何使用category和裝飾者模式分別來實(shí)現(xiàn)圖片的選擇和陰影效果。具體見demo。