設(shè)計(jì)模式——策略模式
策略模式
亦稱:Strategy
意圖
策略模式是一種行為設(shè)計(jì)模式, 它能讓你定義一系列算法, 并將每種算法分別放入獨(dú)立的類中, 以使算法的對(duì)象能夠相互替換。

策略設(shè)計(jì)模式
問(wèn)題
一天, 你打算為游客們創(chuàng)建一款導(dǎo)游程序。該程序的核心功能是提供美觀的地圖, 以幫助用戶在任何城市中快速定位。
用戶期待的程序新功能是自動(dòng)路線規(guī)劃:他們希望輸入地址后就能在地圖上看到前往目的地的最快路線。
程序的首個(gè)版本只能規(guī)劃公路路線。駕車旅行的人們對(duì)此非常滿意。但很顯然, 并非所有人都會(huì)在度假時(shí)開(kāi)車。因此你在下次更新時(shí)添加了規(guī)劃步行路線的功能。此后, 你又添加了規(guī)劃公共交通路線的功能。
而這只是個(gè)開(kāi)始。不久后, 你又要為騎行者規(guī)劃路線。又過(guò)了一段時(shí)間, 你又要為游覽城市中的所有景點(diǎn)規(guī)劃路線。

導(dǎo)游代碼將變得非常臃腫
導(dǎo)游代碼將變得非常臃腫。
盡管從商業(yè)角度來(lái)看, 這款應(yīng)用非常成功, 但其技術(shù)部分卻讓你非常頭疼:每次添加新的路線規(guī)劃算法后, 導(dǎo)游應(yīng)用中主要類的體積就會(huì)增加一倍。終于在某個(gè)時(shí)候, 你覺(jué)得自己沒(méi)法繼續(xù)維護(hù)這堆代碼了。
無(wú)論是修復(fù)簡(jiǎn)單缺陷還是微調(diào)街道權(quán)重, 對(duì)某個(gè)算法進(jìn)行任何修改都會(huì)影響整個(gè)類, 從而增加在已有正常運(yùn)行代碼中引入錯(cuò)誤的風(fēng)險(xiǎn)。
此外, 團(tuán)隊(duì)合作將變得低效。如果你在應(yīng)用成功發(fā)布后招募了團(tuán)隊(duì)成員, 他們會(huì)抱怨在合并沖突的工作上花費(fèi)了太多時(shí)間。在實(shí)現(xiàn)新功能的過(guò)程中, 你的團(tuán)隊(duì)需要修改同一個(gè)巨大的類, 這樣他們所編寫的代碼相互之間就可能會(huì)出現(xiàn)沖突。
解決方案
策略模式建議找出負(fù)責(zé)用許多不同方式完成特定任務(wù)的類, 然后將其中的算法抽取到一組被稱為策略的獨(dú)立類中。
名為上下文的原始類必須包含一個(gè)成員變量來(lái)存儲(chǔ)對(duì)于每種策略的引用。上下文并不執(zhí)行任務(wù), 而是將工作委派給已連接的策略對(duì)象。
上下文不負(fù)責(zé)選擇符合任務(wù)需要的算法——客戶端會(huì)將所需策略傳遞給上下文。實(shí)際上, 上下文并不十分了解策略, 它會(huì)通過(guò)同樣的通用接口與所有策略進(jìn)行交互, 而該接口只需暴露一個(gè)方法來(lái)觸發(fā)所選策略中封裝的算法即可。
因此, 上下文可獨(dú)立于具體策略。這樣你就可在不修改上下文代碼或其他策略的情況下添加新算法或修改已有算法了。

路線規(guī)劃策略。
在導(dǎo)游應(yīng)用中, 每個(gè)路線規(guī)劃算法都可被抽取到只有一個(gè) build-Route生成路線方法的獨(dú)立類中。該方法接收起點(diǎn)和終點(diǎn)作為參數(shù), 并返回路線中途點(diǎn)的集合。
即使傳遞給每個(gè)路徑規(guī)劃類的參數(shù)一模一樣, 其所創(chuàng)建的路線也可能完全不同。主要導(dǎo)游類的主要工作是在地圖上渲染一系列中途點(diǎn), 不會(huì)在意如何選擇算法。該類中還有一個(gè)用于切換當(dāng)前路徑規(guī)劃策略的方法, 因此客戶端 (例如用戶界面中的按鈕) 可用其他策略替換當(dāng)前選擇的路徑規(guī)劃行為。
真實(shí)世界類比

各種前往機(jī)場(chǎng)的出行策略
假如你需要前往機(jī)場(chǎng)。你可以選擇乘坐公共汽車、 預(yù)約出租車或騎自行車。這些就是你的出行策略。你可以根據(jù)預(yù)算或時(shí)間等因素來(lái)選擇其中一種策略。
策略模式結(jié)構(gòu)
1. 上下文 (Con-text) 維護(hù)指向具體策略的引用, 且僅通過(guò)策略接口與該對(duì)象進(jìn)行交流。
2. 策略 (Strat-e-gy) 接口是所有具體策略的通用接口, 它聲明了一個(gè)上下文用于執(zhí)行策略的方法。
3. 具體策略 (Con-crete Strate-gies) 實(shí)現(xiàn)了上下文所用算法的各種不同變體。
4. 當(dāng)上下文需要運(yùn)行算法時(shí), 它會(huì)在其已連接的策略對(duì)象上調(diào)用執(zhí)行方法。上下文不清楚其所涉及的策略類型與算法的執(zhí)行方式。
5. 客戶端 (Client) 會(huì)創(chuàng)建一個(gè)特定策略對(duì)象并將其傳遞給上下文。上下文則會(huì)提供一個(gè)設(shè)置器以便客戶端在運(yùn)行時(shí)替換相關(guān)聯(lián)的策略。
偽代碼
在本例中, 上下文使用了多個(gè)策略來(lái)執(zhí)行不同的計(jì)算操作。
// 策略接口聲明了某個(gè)算法各個(gè)不同版本間所共有的操作。上下文會(huì)使用該接口來(lái)// 調(diào)用有具體策略定義的算法。interface Strategy ismethod execute(a, b)// 具體策略會(huì)在遵循策略基礎(chǔ)接口的情況下實(shí)現(xiàn)算法。該接口實(shí)現(xiàn)了它們?cè)谏舷挛?/span>// 中的互換性。class ConcreteStrategyAdd implements Strategy ismethod execute(a, b) isreturn a + bclass ConcreteStrategySubtract implements Strategy ismethod execute(a, b) isreturn a - bclass ConcreteStrategyMultiply implements Strategy ismethod execute(a, b) isreturn a * b// 上下文定義了客戶端關(guān)注的接口。class Context is// 上下文會(huì)維護(hù)指向某個(gè)策略對(duì)象的引用。上下文不知曉策略的具體類。上下// 文必須通過(guò)策略接口來(lái)與所有策略進(jìn)行交互。private strategy: Strategy// 上下文通常會(huì)通過(guò)構(gòu)造函數(shù)來(lái)接收策略對(duì)象,同時(shí)還提供設(shè)置器以便在運(yùn)行// 時(shí)切換策略。method setStrategy(Strategy strategy) isthis.strategy = strategy// 上下文會(huì)將一些工作委派給策略對(duì)象,而不是自行實(shí)現(xiàn)不同版本的算法。method executeStrategy(int a, int b) isreturn strategy.execute(a, b)// 客戶端代碼會(huì)選擇具體策略并將其傳遞給上下文。客戶端必須知曉策略之間的差// 異,才能做出正確的選擇。class ExampleApplication ismethod main() is創(chuàng)建上下文對(duì)象。讀取第一個(gè)數(shù)。讀取最后一個(gè)數(shù)。從用戶輸入中讀取期望進(jìn)行的行為。if (action == addition) thencontext.setStrategy(new ConcreteStrategyAdd())if (action == subtraction) thencontext.setStrategy(new ConcreteStrategySubtract())if (action == multiplication) thencontext.setStrategy(new ConcreteStrategyMultiply())result = context.executeStrategy(First number, Second number)打印結(jié)果。
策略模式適合應(yīng)用場(chǎng)景
當(dāng)你想使用對(duì)象中各種不同的算法變體, 并希望能在運(yùn)行時(shí)切換算法時(shí), 可使用策略模式。
策略模式讓你能夠?qū)?duì)象關(guān)聯(lián)至可以不同方式執(zhí)行特定子任務(wù)的不同子對(duì)象, 從而以間接方式在運(yùn)行時(shí)更改對(duì)象行為。
當(dāng)你有許多僅在執(zhí)行某些行為時(shí)略有不同的相似類時(shí), 可使用策略模式。
策略模式讓你能將不同行為抽取到一個(gè)獨(dú)立類層次結(jié)構(gòu)中, 并將原始類組合成同一個(gè), 從而減少重復(fù)代碼。
如果算法在上下文的邏輯中不是特別重要, 使用該模式能將類的業(yè)務(wù)邏輯與其算法實(shí)現(xiàn)細(xì)節(jié)隔離開(kāi)來(lái)。
策略模式讓你能將各種算法的代碼、 內(nèi)部數(shù)據(jù)和依賴關(guān)系與其他代碼隔離開(kāi)來(lái)。不同客戶端可通過(guò)一個(gè)簡(jiǎn)單接口執(zhí)行算法, 并能在運(yùn)行時(shí)進(jìn)行切換。
當(dāng)類中使用了復(fù)雜條件運(yùn)算符以在同一算法的不同變體中切換時(shí), 可使用該模式。
策略模式將所有繼承自同樣接口的算法抽取到獨(dú)立類中, 因此不再需要條件語(yǔ)句。原始對(duì)象并不實(shí)現(xiàn)所有算法的變體, 而是將執(zhí)行工作委派給其中的一個(gè)獨(dú)立算法對(duì)象。
實(shí)現(xiàn)方式
1. 從上下文類中找出修改頻率較高的算法 (也可能是用于在運(yùn)行時(shí)選擇某個(gè)算法變體的復(fù)雜條件運(yùn)算符)。
2. 聲明該算法所有變體的通用策略接口。
3. 將算法逐一抽取到各自的類中, 它們都必須實(shí)現(xiàn)策略接口。
4. 在上下文類中添加一個(gè)成員變量用于保存對(duì)于策略對(duì)象的引用。然后提供設(shè)置器以修改該成員變量。上下文僅可通過(guò)策略接口同策略對(duì)象進(jìn)行交互, 如有需要還可定義一個(gè)接口來(lái)讓策略訪問(wèn)其數(shù)據(jù)。
5. 客戶端必須將上下文類與相應(yīng)策略進(jìn)行關(guān)聯(lián), 使上下文可以預(yù)期的方式完成其主要工作。
策略模式優(yōu)缺點(diǎn)
? 你可以在運(yùn)行時(shí)切換對(duì)象內(nèi)的算法。
? 你可以將算法的實(shí)現(xiàn)和使用算法的代碼隔離開(kāi)來(lái)。
? 你可以使用組合來(lái)代替繼承。
? 開(kāi)閉原則。你無(wú)需對(duì)上下文進(jìn)行修改就能夠引入新的策略。
? 如果你的算法極少發(fā)生改變, 那么沒(méi)有任何理由引入新的類和接口。使用該模式只會(huì)讓程序過(guò)于復(fù)雜。
? 客戶端必須知曉策略間的不同——它需要選擇合適的策略。
? 許多現(xiàn)代編程語(yǔ)言支持函數(shù)類型功能, 允許你在一組匿名函數(shù)中實(shí)現(xiàn)不同版本的算法。這樣, 你使用這些函數(shù)的方式就和使用策略對(duì)象時(shí)完全相同, 無(wú)需借助額外的類和接口來(lái)保持代碼簡(jiǎn)潔。
與其他模式的關(guān)系
? 橋接模式、 狀態(tài)模式和策略模式 (在某種程度上包括適配器模式) 模式的接口非常相似。實(shí)際上, 它們都基于組合模式——即將工作委派給其他對(duì)象, 不過(guò)也各自解決了不同的問(wèn)題。模式并不只是以特定方式組織代碼的配方, 你還可以使用它們來(lái)和其他開(kāi)發(fā)者討論模式所解決的問(wèn)題。
? 命令模式和策略看上去很像, 因?yàn)閮烧叨寄芡ㄟ^(guò)某些行為來(lái)參數(shù)化對(duì)象。但是, 它們的意圖有非常大的不同。
? 你可以使用命令來(lái)將任何操作轉(zhuǎn)換為對(duì)象。操作的參數(shù)將成為對(duì)象的成員變量。你可以通過(guò)轉(zhuǎn)換來(lái)延遲操作的執(zhí)行、 將操作放入隊(duì)列、 保存歷史命令或者向遠(yuǎn)程服務(wù)發(fā)送命令等。
? 另一方面, 策略通常可用于描述完成某件事的不同方式, 讓你能夠在同一個(gè)上下文類中切換算法。
? 裝飾模式可讓你更改對(duì)象的外表, 策略則讓你能夠改變其本質(zhì)。
? 模板方法模式基于繼承機(jī)制:它允許你通過(guò)擴(kuò)展子類中的部分內(nèi)容來(lái)改變部分算法。 策略基于組合機(jī)制:你可以通過(guò)對(duì)相應(yīng)行為提供不同的策略來(lái)改變對(duì)象的部分行為。 模板方法在類層次上運(yùn)作, 因此它是靜態(tài)的。 策略在對(duì)象層次上運(yùn)作, 因此允許在運(yùn)行時(shí)切換行為。
? 狀態(tài)可被視為策略的擴(kuò)展。兩者都基于組合機(jī)制:它們都通過(guò)將部分工作委派給 “幫手” 對(duì)象來(lái)改變其在不同情景下的行為。 策略使得這些對(duì)象相互之間完全獨(dú)立, 它們不知道其他對(duì)象的存在。但狀態(tài)模式?jīng)]有限制具體狀態(tài)之間的依賴, 且允許它們自行改變?cè)诓煌榫跋碌臓顟B(tài)。
