1.概述
在軟件開發中也常常遇到類似的情況,實現某一個功能有多種算法或者策略,我們可以根據環境或者條件的不同
選擇不同的算法或者策略來完成該功能。如查找、排序等,一種常用的方法是硬編碼(Hard Coding)在一個類中,
如需要提供多種查找算法,可以將這些算法寫到一個類中,在該類中提供多個方法,
每一個方法對應一個具體的查找算法;當然也可以將這些查找算法封裝在一個統一的
方法中,通過if…else…或者case等條件判斷語句來進行選擇。這兩種實現方法我
們都可以稱之為硬編碼,如果需要增加一種新的查找算法,需要修改封裝算法類
的源代碼;更換查找算法,也需要修改客戶端調用代碼。在這個算法類中封裝了
大量查找算法,該類代碼將較復雜,維護較為困難。如果我們將這些策略包含在
客戶端,這種做法更不可取,將導致客戶端程序龐大而且難以維護,如果存在大
量可供選擇的算法時問題將變得更加嚴重。
例子1:
商場產品銷售:可以正常價格售出,可以打折形式售出,也可以通過積分的形式,不管哪種形式,最后要的就是一個最后的總價
例子2:
出行旅游:我們可以有幾個策略可以考慮:可以騎自行車,汽車,做火車,飛 機。每個策略都可以得到相同的結果,但是它們使用了不同的資源。選擇策略的依據是費用,時間,使用工具還有每種方式的方便程度 。
例子3:
HeadFirst案例:不同的鴨子具有不同的飛行行為,鳴叫行為,不同的飛行行為或鳴叫行為就是不同的算法策略
2.問題
如何讓算法和對象(客戶端)分開來,使得算法可以獨立于使用它的客戶而變化?
3.解決方案
策略模式:
定義一系列的算法,把每一個算法封裝起來, 并且使它們可相互替換。本模式使得算法可獨立于使用它的客戶而變化。也稱為政策模式(Policy)。 策略模式把對象本身和運算規則區分開來,其功能非常強大,因為這個設計模式本身的核心思想就是面向對象編程的多形性的思想。
4.適用性
當存在以下情況時使用Strategy模式
- 許多相關的類僅僅是行為有異。 “策略”提供了一種用多個行為中的一個行為來配置一個類的方法。即一個系統需要動態地在幾種算法中選擇一種。
- 需要使用一個算法的不同變體。例如,你可能會定義一些反映不同的空間 /時間權衡的算法。當這些變體實現為一個算法的類層次時 ,可以使用策略模式。
- 算法使用客戶不應該知道的數據。可使用策略模式以避免暴露復雜的、與算法相關的數據結構。
- 一個類定義了多種行為 , 并且這些行為在這個類的操作中以多個條件語句的形式出現。將相關的條件分支移入它們各自的Strategy類中以代替這些條件語句。
5.swift實現結構圖
swift策略模式.png
6.模式的組成
- 環境類(Context):用一個ConcreteStrategy對象來配置。維護一個對Strategy對象的引用。可定義一個接口來讓Strategy訪問它的數據。
- 抽象策略類(Strategy):定義所有支持的算法的公共接口。 Context使用這個接口來調用某ConcreteStrategy定義的算法。
- 具體策略類(ConcreteStrategy):以Strategy接口實現某具體算法。
7.效果
Strategy模式有下面的一些優點:
- 相關算法系列 Strategy類層次為Context定義了一系列的可供重用的算法或行為。 繼承有助于析取出這些算法中的公共功能。
- 擴展性良好: 繼承提供了另一種支持多種算法或行為的方法。你可以直接生成一個Context類的子類,從而給它以不同的行為。但這會將行為硬行編制到 Context中,而將算法的實現與Context的實現混合起來,從而使Context難以理解、難以維護和難以擴展,而且還不能動態地改變算法。最后你得到一堆相關的類 , 它們之間的唯一差別是它們所使用的算法或行為。 將算法封裝在獨立的Strategy類中使得你可以獨立于其Context改變它,使它易于切換、易于理解、易于擴展。
- 避免使用多重條件判斷 :Strategy模式提供了用條件語句選擇所需的行為以外的另一種選擇。當不同的行為堆砌在一個類中時 ,很難避免使用條件語句來選擇合適的行為。將行為封裝在一個個獨立的Strategy類中消除了這些條件語句。含有許多條件語句的代碼通常意味著需要使用Strategy模式。
- 算法可以自由切換:實現的選擇 Strategy模式可以提供相同行為的不同實現。客戶可以根據不同時間 /空間權衡取舍要求從不同策略中進行選擇。
- 降低耦合:策略以相同的方式調用所有的算法,減少客戶端與算法類之間的耦合
Strategy模式缺點:
- 所有策略類都需要對外暴露: 本模式有一個潛在的缺點,就是一個客戶要選擇一個合適的Strategy就必須知道這些Strategy到底有何不同。此時可能不得不向客戶暴露具體的實現問題。因此僅當這些不同行為變體與客戶相關的行為時 , 才需要使用Strategy模式。
- Strategy和Context之間的通信開銷 :無論各個ConcreteStrategy實現的算法是簡單還是復雜, 它們都共享Strategy定義的接口。因此很可能某些 ConcreteStrategy不會都用到所有通過這個接口傳遞給它們的信息;簡單的 ConcreteStrategy可能不使用其中的任何信息!這就意味著有時Context會創建和初始化一些永遠不會用到的參數。如果存在這樣問題 , 那么將需要在Strategy和Context之間更進行緊密的耦合。
- 策略類會增多:可以通過使用享元模式在一定程度上減少對象的數量。 增加了對象的數目 Strategy增加了一個應用中的對象的數目。有時你可以將 Strategy實現為可供各Context共享的無狀態的對象來減少這一開銷。任何其余的狀態都由 Context維護。Context在每一次對Strategy對象的請求中都將這個狀態傳遞過去。共享的 Strategy不應在各次調用之間維護狀態。
8.實現
抽象類
//這里其實可以用協議替代
class CashSuper: NSObject {
//優點1: 繼承有助于析取出這些算法中的公共功能。
//缺點2: Strategy和Context之間的通信開銷,對于子類實現的通信接口(acceptCash),參數(money)可以會永遠不會使用
func acceptCash(money: Double) -> Double {
return 0.0
}
}
具體實現對象--打折類(其它策略類 類似)
//打折類
class CashRebate: CashSuper {
private var moneyRebate: Double = 1.0
init(moneyRebate: Double) {
self.moneyRebate = moneyRebate
super.init()
}
//打折返回
override func acceptCash(money: Double) -> Double {
return money * moneyRebate
}
}
客戶端對象
//客戶端對象(具體使用的對象)
class CashContext: NSObject {
private var cs: CashSuper!
//優點4: 客戶可以根據不同時間 /空間權衡取舍要求從不同策略中進行選擇。
//優點3: 將算法封裝在獨立的Strategy類中使得你可以獨立于其Context改變它,使它易于切換、易于理解、易于擴展。
//缺點3: 策略模式將造成產生很多策略類
func cashContext(type: CacultorType) {
switch type {
case .Normal:
cs = CashNormal()
case .Rebate:
cs = CashRebate(moneyRebate: 0.8)
case .Return:
cs = CashReturn(moneyCondition: 300, moneyReturn: 100)
}
}
//優點2: 策略以相同的方式調用所有的算法,減少客戶端與算法類之間的耦合
//傳入初始金額,利用私有cs,計算最終金額
func getResult(money: Double) -> Double {
return cs.acceptCash(money: money)
}
}
9.HeadFirst -- 策略模式
class Duck {
//fly 和 quack 不是每個子類都可能具備的所以它們是變化的部分,應該抽取出來
//設計原則:使用組合(FlyBehavior, QuackBehivor)而不是使用繼承
var flyBehavior: FlyBehavior? //針對接口(FlyBehavior)編程而不是針對實現編程
var quackBehavior: QuackBehivor? //針對接口(QuackBehivor)編程而不是針對實現編程
//不需要變化的行為
func swim() {
print("I can swim")
}
//根據子類的而變化的行為
func display() {
}
func performQuack() {
flyBehavior?.fly()
}
func performFly() {
quackBehavior?.quack()
}
}
//具體子類,綠頭鴨
class Mallard: Duck {
override func display() {
print("一只綠頭鴨")
}
}
變化的行為Fly
//設計原則:封裝變化-找出應用中需要變化的地方,將他們獨立出來,不要和不需要變化的代碼混合到一塊
//設計原則:針對接口編程而不是針對實現編程
protocol FlyBehavior {
func fly()
}
extension FlyBehavior {
func fly() {
print("默認實現")
}
}
//下面是針對FlyBehavior協議定義的算法簇
class FlyWithWings: FlyBehavior {
func fly() {
print("I can fly!")
}
}
class FlyNoWay: FlyBehavior {
// func fly() {
// print("I can't fly!")
// }
}
變化的行為Quack
//設計原則:封裝變化-找出應用中需要變化的地方,將他們獨立出來,不要和不需要變化的代碼混合到一塊
//設計原則:針對接口編程而不是針對實現編程
protocol QuackBehivor {
func quack()
}
//下面是針對QuackBehivor協議定義的算法簇
class Quack: QuackBehivor {
func quack() {
print("Quack")
}
}
class Slient: QuackBehivor {
func quack() {
print("slient")
}
}
class Squeak: QuackBehivor {
func quack() {
print("Squeak")
}
}
總結
HeadFirst策略模式使用的設計原則
-
封裝變化
-找出應用中需要變化的地方,將他們獨立出來,不要和不需要變化的代碼混合到一塊 -
針對接口編程而不是針對實現編程
-針對接口編程實際上就是針對超類型編程,其關鍵在于多態,利用多態就可以實現超類型編程,也就是根據實現情況來執行具體的行為,而不是綁定在超類型的行為上。通俗一點來說,就是聲明一個變量,這個變量的類型就是超類型(超類型通過是一個抽象類或一個接口,在swift中接口就是協議,不過和java的接口相比,swift協議可以有默認實現),只要具體實現此超類型的對象都可以直接賦值給上面的變量。也就是說不用關心執行具體的對象類型。 多使用組合而不是繼承
HeadFirst策略模式
策略模式定義了算法族,分別封裝起來,讓他們之前可以相互替換。此模式讓算法的變化獨立于使用算法的客戶。