場景分析
我們在網上購買商品的時候,經常遇到各種打折優惠活動,不同的節假日或者時間優惠策略都不相同,如果讓我們去實現,那麼如何做呢?
常規做法是根據不同的優惠政策,使用if進行判斷,寫很多判斷分支進行處理。類似下面這種。
if (正常價格) {
//具體優惠策略處理
}else if (七折優惠){
//具體優惠策略處理
}else if (國慶節優惠){
//具體優惠策略處理
}else if (優惠策略4條件){
//具體優惠策略處理
}else{
//具體優惠策略處理
}複製程式碼
這種方式雖然可以完成功能,但是缺點很多
- 優惠策略經常改動。那麼每次改動都需要來修改這段程式碼塊,違反開閉原則
- 如果優惠策略非常多,那麼就需要寫很多判斷分支,判斷複雜
- 需要優惠策略功能的客戶程式如果直接包含換行優惠策略的程式碼的話將會變得複雜,這使得客戶程式龐大並且難以維護, 尤其當其需要支援多種優惠策略演算法時問題會更加嚴重
要想避免上面的問題,解決方案就是把每種優惠策略封裝成單獨的類,客戶端需要使用哪個優惠策略,就切換到該策略即可。這就需要使用到我們今天講解的策略模式,下面來具體看看。
定義
定義一系列演算法,把它們一個個的封裝起來,並且使他們可以相互替換。策略模式使得演算法可獨立於使用它的客戶端而變化。
通過定義,我們可以發現策略模式完全就是為解決我們開頭提到的問題而生的。文章開頭的實現方式就是因為把演算法耦合進了客戶端才會導致一系列問題。策略模式就是把演算法和客戶端解耦,把演算法獨立出來,然後給這些演算法提供一個抽象的介面,每個具體的演算法去實現這個介面,客戶端只需要面向介面程式設計,需要使用哪個具體的演算法,就切換到該演算法即可。這樣以後無論如何修改、擴充套件演算法,都不會影響客戶端的實現。
下面就來看看具體的UML結構圖
UML結構圖及說明
注意到結構圖裡面引入了一個context上下文物件,這個物件主要是為了隔離客戶端和具體演算法。context持有一個具體的演算法物件,然後呼叫這個具體演算法。但是context把具體演算法的選擇交給客戶端來執行,自己只是持有客戶端選擇的的具體演算法,然後客戶端呼叫context暴露的介面,context就去呼叫具體的演算法執行功能。
這樣客戶端只需要動態切換演算法,然後設定到context,就可以呼叫不同的演算法。
程式碼實現
上面我們分析瞭如何使用策略模式來完成我們的需求,下面就來看看具體的程式碼實現。
1、定義演算法介面
#import <Foundation/Foundation.h>
@protocol strategyInterface <NSObject>
-(NSInteger)calcPrice:(NSInteger)goodsPrice;
@end複製程式碼
2、具體演算法實現
#import <Foundation/Foundation.h>
#import "strategy.h"
@interface NationalDayStrategy : NSObject<strategyInterface>
@end
===================
#import "NationalDayStrategy.h"
@implementation NationalDayStrategy
-(NSInteger)calcPrice:(NSInteger)goodsPrice{
return goodsPrice * 0.5 - 12;
}
@end複製程式碼
上面只實現了國慶節優惠策略,其他的策略類似,具體看demo。
3、實現context
#import <Foundation/Foundation.h>
#import "strategy.h"
@interface Price : NSObject
@property(strong,nonatomic)id<strategyInterface> strategy;
- (instancetype)initWithStrategy:(id<strategyInterface>)strategy;
- (NSInteger)quotePirce:(NSInteger)goodsPrice;
@end
===================================
#import "Price.h"
@implementation Price
- (instancetype)initWithStrategy:(id<strategyInterface>)strategy
{
self = [super init];
if (self) {
self.strategy = strategy;
}
return self;
}
-(NSInteger)quotePirce:(NSInteger)goodsPrice{
return [self.strategy calcPrice:goodsPrice];
}
@end複製程式碼
4、測試
id<strategyInterface> strategy = [NationalDayStrategy new];
Price *quote = [[Price alloc]initWithStrategy:strategy];
NSInteger quotePrice = [quote quotePirce:10002];
NSLog(@"處理後的商品價格為:%zd", quotePrice);複製程式碼
如果需要換成其他優惠策略,只需要做如下更改即可:
id<strategyInterface> strategy = [NationalDayStrategy new];
改成:
id<strategyInterface> strategy = [discountStrategy new];複製程式碼
5、說明
上面的實現過程,我們把本來的商品價格作為引數通過context傳遞到具體的演算法。還有一種情況我們可以把context本身作為引數傳遞給具體演算法,這樣後者就可以在合適的情況回撥context。
使用時機
- 許多相關的類僅僅是行為有異。“策略”提供了一種用多個行為中的一個行為來配置一
個類的方法。
- 需要使用一個演算法的不同變體。例如,你可能會定義一些反映不同的空間 /時間權衡的
演算法。當這些變體實現為一個演算法的類層次時 ,可以使用策略模式。
- 演算法使用客戶不應該知道的資料。可使用策略模式以避免暴露覆雜的、與演算法相關的數
據結構。
- 一個類定義了多種行為 , 並且這些行為在這個類的操作中以多個條件語句的形式出現。
將相關的條件分支移入它們各自的 strategy 類中以代替這些條件語句。
優缺點
相 關 算 法 系 列 strategy類層次為 context 定 義 了 一 系 列 的 可 供 重 用 的 算 法 或 行 為 。 可以使用繼承提取出這些演算法中的公共功能。
一個替代繼承的方式。你可以使用繼承提供另一種支援多種演算法或行為的方法,首先直接生成一個 context 類 的 子 類 , 然後 給 它 以 不 同 的 行 為,但 這 會 將 行 為 硬 行 編 制 到context 中。而將 演算法的實現與context的實現混合起來 , 從而使 context 難 以 理 解 、 難 以 維 護 和 難 以 擴 展 , 而 且 還不能動態地改變演算法。最後你得到一堆相關的類 , 它們之間的唯一差別只是它們所使用的演算法或行為的不同。而將演算法封裝在獨立的 strategy 類中,使得你可以獨立於其context改變它,使它易於切換、 易於理解、易於擴充套件。
消除了一些條件語句。strategy模 式 提 供 了 用 條 件 語 句 選 擇 所 需 的 行 為 以 外 的 另 一 種 選 擇。當不同的行為堆砌在一個類中時 , 很難避免使用條件語句來選擇合適的行為。將行為封裝 在一個個獨立的 strategy 類 中 消 除 了 這 些 條 件 語 句 。
實現的選擇 S t r a t e g y 模式可以提供相同行為的不同實現。客戶可以根據不同時間 / 空間 權衡取捨要求從不同策略中進行選擇。
客戶必須瞭解不同的 S t r a t e g y。 本 模 式 有 一 個 潛 在 的 缺 點 , 就 是 一 個 客 戶 要 選 擇 一 個 合 適的 S t r a t e g y 就必須知道這些 S t r a t e g y 到 底 有 何 不 同 。 此 時 可 能 不 得 不 向 客 戶 暴 露 具 體 的 實 現 問題。因此僅當這些不同行為變體與客戶相關的行為時 , 才需要使用 S t r a t e g y 模式。
- 增加了物件的數目 。你需要為每個不同的具體的S t r a t e g y增加了一個新的類,這就增加了應用中的物件的數目。此時你可以將 S t r a t e g y 實 現為可供各 C o n t e x t共享的無狀態的物件來減少這一開銷。任何其餘的狀態都由 C o n t e x t 維護。C o n t e x t 在每一次對 S t r a t e g y 物件的請求中都將這個狀態傳遞過去。共享的 S t r a g e y不應在各次 呼叫之間維護狀態。 F l y w e i g h t 模式更詳細地描述了這一方法。