目錄
本文的結(jié)構(gòu)如下:
- 引言
- 什么是策略模式
- 模式的結(jié)構(gòu)
- 典型代碼
- 代碼示例
- 策略模式和模板方法模式的區(qū)別
- 優(yōu)點(diǎn)和缺點(diǎn)
- 適用環(huán)境
- 模式應(yīng)用
一莲蜘、引言
寫(xiě)這篇文章的時(shí)間是17年11月18號(hào)上午9點(diǎn)半谭确,NBA正打得火熱,騎士VS76人票渠,詹韋對(duì)陣大帝和西帝逐哈,內(nèi)線孱弱的騎士被大帝蹂躪,欲生欲死问顷;再看火箭VS籃網(wǎng)昂秃,第一節(jié)哈登7中7,三分5中5,狂砍20分杜窄;又有步行者VS魔術(shù)肠骆,奧拉迪波10中10......可謂精彩。
我們的話題由此展開(kāi)塞耕。
籃球而言蚀腿,對(duì)陣雙方4節(jié)比賽結(jié)束后,哪隊(duì)分?jǐn)?shù)高扫外,哪隊(duì)獲勝莉钙,打平加時(shí)繼續(xù)較量。所以筛谚,說(shuō)到最后磁玉,籃球是一個(gè)得分的比賽,而得分的方式又有許多種驾讲,庫(kù)日天的三分蜀涨,老詹的上籃,德羅贊的中投蝎毡,哈登的罰球(我不是黑,不要噴我氧枣,只是選擇比較有代表性的得分方式)沐兵。具體的得分方式根據(jù)防守而定,籃下空無(wú)一人果斷暴扣便监,三分線外沒(méi)人防守扎谎,三分扔起,當(dāng)然老漢日常碧浪就不說(shuō)了......
在軟件開(kāi)發(fā)中烧董,也有很多類(lèi)似的場(chǎng)景毁靶,實(shí)現(xiàn)某一個(gè)功能有多條途徑,每一條途徑對(duì)應(yīng)一種算法逊移,此時(shí)可以使用一種設(shè)計(jì)模式來(lái)實(shí)現(xiàn)靈活地選擇解決途徑预吆,也能夠方便地增加新的解決途徑。這就是策略模式胳泉。
二拐叉、什么是策略模式
在策略模式中岩遗,定義了一些獨(dú)立的類(lèi)來(lái)封裝不同的算法,每一個(gè)類(lèi)封裝一種具體的算法凤瘦,在這里宿礁,每一個(gè)封裝算法的類(lèi)都可以稱(chēng)之為一種策略(Strategy),為了保證這些策略在使用時(shí)具有一致性蔬芥,一般會(huì)提供一個(gè)抽象的策略類(lèi)來(lái)做規(guī)則的定義梆靖,而每種算法則對(duì)應(yīng)于一個(gè)具體策略類(lèi)。
策略模式的主要目的是將算法的定義與使用分開(kāi)笔诵,也就是將算法的行為和環(huán)境分開(kāi)返吻,將算法的定義放在專(zhuān)門(mén)的策略類(lèi)中,每一個(gè)策略類(lèi)封裝了一種實(shí)現(xiàn)算法嗤放,使用算法的環(huán)境類(lèi)針對(duì)抽象策略類(lèi)進(jìn)行編程思喊,符合“依賴(lài)倒轉(zhuǎn)原則”。在出現(xiàn)新的算法時(shí)次酌,只需要增加一個(gè)新的實(shí)現(xiàn)了抽象策略類(lèi)的具體策略類(lèi)即可恨课。
策略模式定義如下:
策略模式(Strategy Pattern):定義一系列算法類(lèi),將每一個(gè)算法封裝起來(lái)岳服,并讓它們可以相互替換剂公,策略模式讓算法獨(dú)立于使用它的客戶(hù)而變化,也稱(chēng)為政策模式(Policy)吊宋。策略模式是一種對(duì)象行為型模式纲辽。
三、模式的結(jié)構(gòu)
策略模式的UML類(lèi)圖如下:
在策略模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:
- Context(環(huán)境類(lèi)):環(huán)境類(lèi)是使用算法的角色璃搜,它在解決某個(gè)問(wèn)題(即實(shí)現(xiàn)某個(gè)方法)時(shí)可以采用多種策略拖吼。在環(huán)境類(lèi)中維持一個(gè)對(duì)抽象策略類(lèi)的引用實(shí)例,用于定義所采用的策略这吻。
- Strategy(抽象策略類(lèi)):它為所支持的算法聲明了抽象方法吊档,是所有策略類(lèi)的父類(lèi),它可以是抽象類(lèi)或具體類(lèi)唾糯,也可以是接口怠硼。環(huán)境類(lèi)通過(guò)抽象策略類(lèi)中聲明的方法在運(yùn)行時(shí)調(diào)用具體策略類(lèi)中實(shí)現(xiàn)的算法。
- ConcreteStrategy(具體策略類(lèi)):它實(shí)現(xiàn)了在抽象策略類(lèi)中聲明的算法移怯,在運(yùn)行時(shí)香璃,具體策略類(lèi)將覆蓋在環(huán)境類(lèi)中定義的抽象策略類(lèi)對(duì)象,使用一種具體的算法實(shí)現(xiàn)某個(gè)業(yè)務(wù)處理舟误。
四葡秒、典型代碼
策略模式是一個(gè)比較容易理解和使用的設(shè)計(jì)模式,策略模式是對(duì)算法的封裝,它把算法的責(zé)任和算法本身分割開(kāi)同云,委派給不同的對(duì)象管理糖权。策略模式通常把一個(gè)系列的算法封裝到一系列具體策略類(lèi)里面,作為抽象策略類(lèi)的子類(lèi)炸站。環(huán)境類(lèi)是需要使用算法的類(lèi)星澳。在一個(gè)系統(tǒng)中可以存在多個(gè)環(huán)境類(lèi),它們可能需要重用一些相同的算法旱易。
在使用策略模式時(shí)禁偎,首先應(yīng)該創(chuàng)建一個(gè)抽象策略類(lèi),其典型代碼如下所示:
public abstract class AbstractStrategy {
public abstract void algorithm();
}
封裝每一種具體算法的類(lèi)作為該抽象策略類(lèi)的子類(lèi)阀坏,期典型代碼如下:
public class ConcreteStrategyA extends AbstractStrategy {
public void algorithm() {
//todo
}
}
public class ConcreteStrategyB extends AbstractStrategy {
public void algorithm() {
//todo
}
}
在Context類(lèi)與抽象策略類(lèi)之間建立一個(gè)關(guān)聯(lián)關(guān)系如暖,其典型代碼如下:
public class Context {
private AbstractStrategy strategy;
public Context(AbstractStrategy strategy){
this.strategy = strategy;
}
public void algorithm(){
strategy.algorithm();
}
}
客戶(hù)端代碼如下:
public class Client {
public static void main(String[] args) {
AbstractStrategy strategy = new ConcreteStrategyA();
Context context = new Context(strategy);
context.algorithm();
}
}
也可以將具體策略類(lèi)類(lèi)名存儲(chǔ)在配置文件中,通過(guò)反射來(lái)動(dòng)態(tài)創(chuàng)建具體策略對(duì)象忌堂,從而使得用戶(hù)可以靈活地更換具體策略類(lèi)盒至,增加新的具體策略類(lèi)也很方便募谎。策略模式提供了一種可插入式(Pluggable)算法的實(shí)現(xiàn)方案心墅。
五、代碼示例
這里以一個(gè)球員上場(chǎng)打球?yàn)槔f(shuō)明竞川。
5.1棋嘲、不用策略模式
設(shè)計(jì)一個(gè)球員類(lèi):
public class Player {
private String defensive;//對(duì)手的防守情況
public Player(){
}
public void setDefensive(String defensive){
this.defensive = defensive;
}
public void score(){
if ("undefended".equalsIgnoreCase(defensive)){
System.out.println("籃下無(wú)人防守酒唉,他起飛,大風(fēng)車(chē)戰(zhàn)斧扣籃沸移,但是扣飛了痪伦,得0分,what a pity!");
}else if("foul".equalsIgnoreCase(defensive)){
System.out.println("他身上掛著三個(gè)人雹锣,強(qiáng)起上籃命中网沾,哨響了,and one蕊爵,他走上罰球線绅这,加罰不中,得兩分");
}else if("1s".equalsIgnoreCase(defensive)){
System.out.println("比賽剩下最后1s在辆,還落后2分,現(xiàn)在比賽回來(lái)度苔,他接到了球匆篓,沒(méi)時(shí)間了,后撤步直接射三分寇窑,有沒(méi)有鸦概?命中了,絕殺,絕殺窗市。");
}
}
}
客戶(hù)端測(cè)試:
public class Client {
public static void main(String[] args) {
Player player = new Player();
String defensive;//對(duì)手防守情況
player.setDefensive("undefended");
player.score();
System.out.println("------------------------");
player.setDefensive("foul");
player.score();
System.out.println("------------------------");
player.setDefensive("1s");
player.score();
System.out.println("------------------------");
}
}
這段代碼實(shí)現(xiàn)了根據(jù)不同防守得分的方式先慷,防守方式不同,直接修改客戶(hù)端的參數(shù)咨察,而不需修改源碼论熙,但還是有一些問(wèn)題:
- 它包含各種得分的方式,代碼很多且復(fù)雜摄狱,if...else...多脓诡,不利于維護(hù)和擴(kuò)展。
- 增加新的得分方式必須修改源碼媒役,違反了“開(kāi)閉原則”祝谚,系統(tǒng)的靈活性和可擴(kuò)展性較差。
- 復(fù)用性差酣衷,如果另一個(gè)球員想要復(fù)用這些得分方式交惯,只能將源碼粘貼復(fù)制,無(wú)法單獨(dú)重用其中的某個(gè)或某些算法(重用較為麻煩)穿仪。
5.1席爽、使用策略模式
重構(gòu)后Basket充當(dāng)抽象策略類(lèi),F(xiàn)reeThrows牡借、SlamDunk和ThreePointer充當(dāng)具體策略類(lèi)拳昌,Player充當(dāng)環(huán)境類(lèi)。
代碼如下:
策略類(lèi):
public abstract class Basket {
public abstract void score();
}
public class SlamDunk extends Basket{
public void score() {
System.out.println("籃下無(wú)人防守钠龙,他起飛炬藤,大風(fēng)車(chē)戰(zhàn)斧扣籃,但是扣飛了碴里,得0分沈矿,what a pity!");
}
}
public class FreeThrows extends Basket{
public void score() {
System.out.println("他身上掛著三個(gè)人,強(qiáng)起上籃命中咬腋,哨響了羹膳,and one,他走上罰球線根竿,加罰不中陵像,得兩分");
}
}
public class ThreePointer extends Basket {
public void score() {
System.out.println("比賽剩下最后1s,還落后2分寇壳,現(xiàn)在比賽回來(lái)醒颖,他接到了球,沒(méi)時(shí)間了壳炎,后撤步直接射三分泞歉,有沒(méi)有?命中了,絕殺腰耙,絕殺榛丢。");
}
}
環(huán)境類(lèi):
public class Player {
private Basket basket;
public Player(Basket basket){
this.basket = basket;
}
public void score(){
basket.score();
}
}
客戶(hù)端:
public class Client {
public static void main(String[] args) {
Basket basket = new ThreePointer();
Player player = new Player(basket);
player.score();
}
}
重構(gòu)后的代碼,如果需要增加新的得分方式挺庞,原有代碼均無(wú)須修改晰赞,只要增加一個(gè)新的得分類(lèi)作為抽象得分類(lèi)的子類(lèi),實(shí)現(xiàn)在抽象得分類(lèi)中聲明的得分方法挠阁,然后可以通過(guò)配置文件的方式更換具體子類(lèi)宾肺,完全符合“開(kāi)閉原則”。
六侵俗、策略模式和模板方法模式的區(qū)別
Template Method模式是將相同的算法放在一個(gè)類(lèi)中,將算法變化的部分放在子類(lèi)中實(shí)現(xiàn)锨用,策略模式是將不同的算法用不同的策略類(lèi)表示,似乎沒(méi)有太大的區(qū)別隘谣。
認(rèn)真想想還是有區(qū)別的增拥。
策略模式的策略類(lèi)中的方法一般是public的,封裝的算法是任意給用戶(hù)使用寻歧,而模板方法模式中的虛方法更多的是有限制的掌栅,一般不希望被外部調(diào)用,而是在模板方法中在固定的順序位置被調(diào)用码泛。
七猾封、優(yōu)點(diǎn)和缺點(diǎn)
7.1、優(yōu)點(diǎn)
策略模式的主要優(yōu)點(diǎn)如下:
- 策略模式提供了對(duì)“開(kāi)閉原則”的完美支持噪珊,用戶(hù)可以在不修改原有系統(tǒng)的基礎(chǔ)上選擇算法或行為晌缘,也可以靈活地增加新的算法或行為。
- 策略模式提供了管理相關(guān)的算法族的辦法痢站。策略類(lèi)的等級(jí)結(jié)構(gòu)定義了一個(gè)算法或行為族磷箕,恰當(dāng)使用繼承可以把公共的代碼移到抽象策略類(lèi)中,從而避免重復(fù)的代碼阵难。
- 策略模式提供了一種可以替換繼承關(guān)系的辦法岳枷。如果不使用策略模式,那么使用算法的環(huán)境類(lèi)就可能會(huì)有一些子類(lèi)呜叫,每一個(gè)子類(lèi)提供一種不同的算法空繁。但是,這樣一來(lái)算法的使用就和算法本身混在一起朱庆,不符合“單一職責(zé)原則”盛泡,決定使用哪一種算法的邏輯和該算法本身混合在一起,從而不可能再獨(dú)立演化椎工;而且使用繼承無(wú)法實(shí)現(xiàn)算法或行為在程序運(yùn)行時(shí)的動(dòng)態(tài)切換。
- 使用策略模式可以避免多重條件選擇語(yǔ)句。多重條件選擇語(yǔ)句不易維護(hù)维蒙,它把采取哪一種算法或行為的邏輯與算法或行為本身的實(shí)現(xiàn)邏輯混合在一起掰吕,將它們?nèi)坑簿幋a(Hard Coding)在一個(gè)龐大的多重條件選擇語(yǔ)句中,比直接繼承環(huán)境類(lèi)的辦法還要原始和落后颅痊。
- 策略模式提供了一種算法的復(fù)用機(jī)制殖熟,由于將算法單獨(dú)提取出來(lái)封裝在策略類(lèi)中,因此不同的環(huán)境類(lèi)可以方便地復(fù)用這些策略類(lèi)斑响。
7.2菱属、缺點(diǎn)
策略模式的主要缺點(diǎn)如下:
- 客戶(hù)端必須知道所有的策略類(lèi),并自行決定使用哪一個(gè)策略類(lèi)舰罚。這就意味著客戶(hù)端必須理解這些算法的區(qū)別纽门,以便適時(shí)選擇恰當(dāng)?shù)乃惴āQ言之营罢,策略模式只適用于客戶(hù)端知道所有的算法或行為的情況赏陵。
- 策略模式將造成系統(tǒng)產(chǎn)生很多具體策略類(lèi),任何細(xì)小的變化都將導(dǎo)致系統(tǒng)要增加一個(gè)新的具體策略類(lèi)饲漾。
- 無(wú)法同時(shí)在客戶(hù)端使用多個(gè)策略類(lèi)蝙搔,也就是說(shuō),在使用策略模式時(shí)考传,客戶(hù)端每次只能使用一個(gè)策略類(lèi)吃型,不支持使用一個(gè)策略類(lèi)完成部分功能后再使用另一個(gè)策略類(lèi)來(lái)完成剩余功能的情況。
八僚楞、適用環(huán)境
在以下情況下可以考慮使用策略模式:
- 如果在一個(gè)系統(tǒng)里面有許多類(lèi)勤晚,它們之間的區(qū)別僅在于它們的行為,那么使用策略模式可以動(dòng)態(tài)地讓一個(gè)對(duì)象在許多行為中選擇一種行為镜硕。
- 一個(gè)系統(tǒng)需要?jiǎng)討B(tài)地在幾種算法中選擇一種运翼,那么可以將這些算法封裝到一個(gè)個(gè)的具體算法類(lèi)中,而這些具體算法類(lèi)都是一個(gè)抽象算法類(lèi)的子類(lèi)兴枯。
- 一個(gè)對(duì)象有很多的行為血淌,如果不用恰當(dāng)?shù)哪J剑@些行為就只好使用多重條件選擇語(yǔ)句來(lái)實(shí)現(xiàn)财剖。此時(shí)悠夯,使用策略模式,把這些行為轉(zhuǎn)移到相應(yīng)的具體策略類(lèi)里面躺坟,就可以避免使用難以維護(hù)的多重條件選擇語(yǔ)句沦补。
- 不希望客戶(hù)端知道復(fù)雜的、與算法相關(guān)的數(shù)據(jù)結(jié)構(gòu)咪橙,在具體策略類(lèi)中封裝算法與相關(guān)的數(shù)據(jù)結(jié)構(gòu)夕膀,可以提高算法的保密性與安全性虚倒。
九、模式應(yīng)用
Java SE的容器布局管理就是策略模式的一個(gè)經(jīng)典應(yīng)用實(shí)例产舞,其基本結(jié)構(gòu)如下圖: