更多請移步:我的博客
意圖
Strategy是行爲模式的一種,讓你定義一組算法,各自封裝,並且他們可替換。Strategy讓這些算法獨立與使用他們的客戶端。
問題
一天你決定寫一個給驢友使用的導航應用。這個應用以漂亮的地圖爲中心,允許用戶在任何城市快速的定位。應用最大的特點是能夠自動規劃路線,所以你決定特別關注這點。用戶可以輸入一個期望的目的地,能夠快速在屏幕上畫出路線。
第一個版本的應用只能規劃道路上的路線,適合汽車旅客。但顯然不是所有的人都喜歡在休假時開車。所以下一次更新,你添加了規劃步行路線的選項。之後,你又增加了一個選項,允許用戶規劃基於公共交通的路線。
但這僅是一個開始。最近的版本中你計劃增加一個可以規劃自行車路線的特性。之後,根據沿途景點規劃路線成爲可選項。
這款應用的業務是成功的,但是技術部分讓你頭疼不已。
每次增加一個新的路線算法,Map
類的大小就會增長。至此,應用開始變得難以維護。
任何搜索算法的改變,比如修復一個BUG或者微調算法行爲,都會影響整個類,增加了讓已存在代碼出錯的風險。
最終,團隊合作變得低效。你的隊友在你成功發佈一次之後都會抱怨要花費大量的時間在處理代碼合併衝突上,因爲它們都和一個大類相關聯。
解決
策略模式建議採使用一個以許多不同方式做重要事情的類,並將這些算法分別提取到單獨的被稱爲策略的類中。
源類被稱爲context,它有一個字段來接受存儲所有策略中的一種。上下文把工作委託給關聯的策略,而不是自己去執行。
上下文沒有選擇合適算法的職責,客戶端負責傳遞一個適當的策略到上下文中。
事實上,上下文不知道策略的細節。上下文只通過策略基本接口暴漏出來的方法和它通信。這是的上下文獨立與策略,允許你在不修改上下文或者其他策略的情況下添加新的策略。
在我們導航應用中,每個路線算法都將被抽離到它們自己對應的只有一個buildRoute
方法的類中。這個方法接受出發地和目的地,返回路線檢查點的集合。
甚至每個路線類在相同的參數下會給出不同的路線。Map
類不需要在關心那個策略被選中,因爲它唯一的工作就是把檢查點渲染到地圖上。
這個Map
將會提供出一個方法來切換路線策略,這樣客戶端就可以提供出一個按鈕給用戶來修改當前規劃路線的方式。
真實世界的類比
運輸
你必須到機場去。你可以坐巴士,打的或者騎單車。出行方式就是策略。你根據上下文來選擇一個策略,比如預算或者時間限制。
結構
Context存儲一個Concrete Strategy對象,但是隻通過通用的Strategy接口和其協作。上下文應當報漏出一個setter方法讓其他對象可以替換關聯的策略對象。
Strategy爲所有的策略聲明瞭一個通用接口。這個接口讓具體的策略在上下文中是可替換的。
Concrete Strategy實現了不同的算法,旨在以不同的方式完成相同的工作。
上下文只在它需要的時候去調用策略來執行任務。但它並不知道到底是那個策略在執行。
Client會根據不同的場景選擇不同的策略。他們可以在運行時根據需要來配置上下文的策略。
僞代碼
在這個例子中,上下文使用策略來進行不同的算數運算。
// Common interface for all strategies.
interface Strategy is
method execute(a, b)
// Each concrete strategy provides unique implementation.
class ConcreteStrategyAdd implements Strategy is
method execute(a, b) is
return a + b
class ConcreteStrategySubtract implements Strategy is
method execute(a, b) is
return a - b
class ConcreteStrategyMultiply implements Strategy is
method execute(a, b) is
return a * b
// Context (as a client) always works with strategies
// through a common interface. It does not know or care
// which strategy is currently active.
class Context is
private strategy: Strategy
method setStrategy(Strategy strategy) is
this.strategy = strategy
method executeStrategy(int a, int b) is
return strategy.execute(a, b)
// The concrete strategy is picked on a higher level (for
// example, by application config) and passed to the client
// object. At any time, the strategy object can be replaced
// by a different strategy.
class ExampleApplication is
method main() is
Create context object.
Read first number.
Read last number.
Read the desired action from user input.
if (action == addition) then
context.setStrategy(new ConcreteStrategyAdd())
if (action == subtraction) then
context.setStrategy(new ConcreteStrategySubtract())
if (action == multiplication) then
context.setStrategy(new ConcreteStrategyMultiply())
result = context.executeStrategy(First number, Second number)
Print result.
適用性
當你有一個對象需要以許多不同的方式來做相同的任務時。
策略模式允許你在運行時通過提供不同的子對象(實際處理工作的對象)來修改對象的行爲。
當你有許多相似的類,它們以不同的方式執行一些行爲。
策略模式允許你將所有這些類的行爲抽取到單獨的類層次結構中,從而使原始類的行爲可以自定義。
當你不想把算法實現的細節暴露給其他類時。
策略模式通過把代碼,內部數據和算法的依賴關係提取到自己的類中來隔離其他對象。
一個算法通過大量條件操作被選擇執行。背個條件分支代表不同的算法。
策略允許你通過抽離每個算法到自己的類中來分解條件,這些類都遵循一個相同的接口。上下文把工作委託給這些對象,而不是自己實現這些行爲。
如何實現
確定客戶希望通過“flex ponit”訪問的算法。
爲這個算法所有的實現聲明通用的接口。
挨個把這些算法抽離到它們自己的類中。它們都應當遵循通用的Strategy接口。
給Context類增加一個字段來關聯當前使用的策略,並提供一個setter方法用來改變策略。上下文應當只通過Strategy接口和策略協作。
當客戶端需要上下文來以某種方式工作的時候,必須提供一個合適策略對象。
優點
允許在運行時熱替換算法。
對其他類隔離代碼和算法數據。
用委託替代繼承。
符合開閉原則。
缺點
通過創建多個額外類增加代碼複雜度。
客戶端必須關心策略間的差異以便選擇正確的策略。
和其他模式的關係
State,Strategy,Bridge(和某種程度上的Adapter)都有相似的解決結構。它們都是共享“handle/body”句柄。它們的意圖不一樣,所以他們用來解決不同的問題。
Command和Strategy很相似,因爲他們都是把一些行爲參數化到上下文。Command可以用愛把任何操作轉換到一個對象中。可選的參數變成了那個對象的字段。這個轉換允許延遲或者遠程執行,存儲命令歷史等。
另一方面,Strategy模式通常用來描述處理相同事情的不同方式。它可以幫助我們讓這些算法在一個單獨的上下文類中是可替換的。
Decorator可以讓你改變一個對象的皮膚(外部表象)。Strategy讓你改變膽子(內部實現)。
Template Method使用繼承來改變算法(子類擴展父類的某些方法)。Strategy通過把工作委託給可替換的策略對象來改變對象的行爲。模版方法模式工作在類這一層面。策略模式允許你改變個性化對象的行爲。
State可以看作是Strategy模式的一種擴展。這兩種模式都採用組合的方式來改變主對象的行爲,主對象委託工作給這些幫助對象。Strategy模式讓這些對象完全獨立。State模式允許狀態對象改變上下文對象的當前狀態,他們之間是相互依存的。
小結
策略模式是行爲模式的一種,他把一系列行爲分解到不同的對象中,使得他們在源上下文對象中可以互換。
源對象被叫做上下文,他會持有一個策略對象的引用,並把任務委託給策略執行。爲了改變上下文處理工作的方式,其他對象可以修改當前上下文中的策略對象。