設(shè)計(jì)模式--策略模式

目錄

本文的結(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)圖如下:

20171128_strategy01.png

在策略模式結(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)。

20171128_strategy02.png

代碼如下:

策略類(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)如下圖:

20171128_strategy03.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末魂奥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子易猫,更是在濱河造成了極大的恐慌耻煤,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件准颓,死亡現(xiàn)場(chǎng)離奇詭異哈蝇,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)攘已,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)炮赦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人贯被,你說(shuō)我怎么就攤上這事眼五。” “怎么了彤灶?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵看幼,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我幌陕,道長(zhǎng)诵姜,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任搏熄,我火速辦了婚禮棚唆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘心例。我一直安慰自己宵凌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布止后。 她就那樣靜靜地躺著瞎惫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪译株。 梳的紋絲不亂的頭發(fā)上瓜喇,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音歉糜,去河邊找鬼乘寒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛匪补,可吹牛的內(nèi)容都是我干的伞辛。 我是一名探鬼主播烂翰,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蚤氏!你這毒婦竟也來(lái)了刽酱?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瞧捌,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后润文,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體姐呐,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年典蝌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了曙砂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡骏掀,死狀恐怖鸠澈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情截驮,我是刑警寧澤笑陈,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站葵袭,受9級(jí)特大地震影響涵妥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坡锡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一蓬网、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鹉勒,春花似錦帆锋、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至绵疲,卻和暖如春哲鸳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盔憨。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工徙菠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人郁岩。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓婿奔,卻偏偏與公主長(zhǎng)得像缺狠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子萍摊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 概念及定義 概念在完成某一功能時(shí)挤茄,有時(shí)需要根據(jù)不同環(huán)境采取不同的策略或行為。將這些不同的策略或行為(稱(chēng)為算法)一一...
    maxwellyue閱讀 523評(píng)論 0 0
  • 定義 策略模式屬于對(duì)象的行為模式冰木。其用意是針對(duì)一組算法穷劈,將每一個(gè)算法封裝到具有共同接口的獨(dú)立的類(lèi)中,從而使得它們可...
    步積閱讀 767評(píng)論 0 2
  • 模式動(dòng)機(jī) 完成一項(xiàng)任務(wù)踊沸,往往可以有多種不同的方式歇终,每一種方式稱(chēng)為一個(gè)策略,我們可以根據(jù)環(huán)境或者條件的不同選擇不同的...
    lever_xu閱讀 339評(píng)論 0 0
  • 試著不為明天而煩惱逼龟, 不為昨天而嘆息评凝, 只為今天更美好。 試著用希望迎接朝霞腺律, 一切都是恰到好處奕短。
    袁益君閱讀 164評(píng)論 0 1