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

本文參照《Head First 設(shè)計(jì)模式》竿奏,轉(zhuǎn)載請(qǐng)注明出處
對(duì)于整個(gè)系列,我們按照這本書的設(shè)計(jì)邏輯眶根,使用情景分析的方式來描述蜀铲,并且穿插使用一些問題,總結(jié)的方式來講述属百。并且所有的開發(fā)源碼记劝,都會(huì)托管到github上。
項(xiàng)目地址:https://github.com/jixiang52002/HFDesignPattern

1.引文

Joe的公司是做模擬鴨子活動(dòng)的游戲而出名族扰,這款游戲取名為SimUDuck厌丑,這款游戲具有非常多的鴨子定欧,一邊游泳一邊呱呱叫。這里的設(shè)計(jì)采用了標(biāo)準(zhǔn)的OO(Object Oriented怒竿,面向?qū)ο螅┑姆绞骄帉懣仇@里有一個(gè)鴨子的超類(SuperClass),后續(xù)所有的鴨子都必須繼承這個(gè)超類耕驰。

1.1OO面向?qū)ο竽P?/h2>
鴨子OO模型

1.2繼承

這時(shí)爷辱,公司的高層們想要通過模擬會(huì)飛的鴨子來追求行業(yè)的領(lǐng)先。然后Joe的項(xiàng)目經(jīng)理拍著胸脯告訴主管朦肘,Joe很快就可以搞定饭弓,“有了OO什么都不怕”
Joe接收到任務(wù)后,想出了一個(gè)辦法:“我僅需要在Duck這個(gè)超類中加上Fly()的方法厚骗,然后所有的鴨子都可以飛了”示启。然后他的設(shè)計(jì)模型就改成以下的樣子


增加fly方法后的模型

這樣看起來貌似沒有什么問題的,然后可怕的問題發(fā)生了领舰。夫嗓。。冲秽。
用戶反饋舍咖,自己的橡皮鴨和木鴨居然也可以飛起來!o鄙!排霉!
那么,到底是為何導(dǎo)致了這個(gè)可怕的問題民轴?
我們來分析一下:由于Joe在Duck超類中加上fly方法攻柠,導(dǎo)致所有的子類都會(huì)繼承該方法,這就導(dǎo)致了原本不會(huì)飛的橡皮鴨和木鴨也具有飛行能力后裸,顯然為了提高復(fù)用性使用的繼承方式瑰钮,并未達(dá)到完美得結(jié)果。

1.3 繼承+覆蓋

Joe在思考后微驶,又得出一個(gè)方案浪谴,那就是在橡皮鴨中將fly方法覆蓋掉,不做任何操作因苹,這樣原有的RubberDuck類中架構(gòu)就變成如下:


RubberDuck

然后苟耻,業(yè)務(wù)的需求又需要加入木鴨(DecoyDuck),它不會(huì)叫也不會(huì)飛扶檐⌒渍龋苦逼的Joe又要把木鴨(DecoyDuck)中quark方法覆蓋,這樣DecoyDuck中的類結(jié)構(gòu)就變成如下:


DecoyDuck

但是我們會(huì)發(fā)覺款筑,如果以后有新的業(yè)務(wù)官卡,甚至于fly方法中出現(xiàn)一個(gè)bug蝗茁,或者需要?jiǎng)h除fly相關(guān)的業(yè)務(wù),所有相關(guān)代碼都需要修改寻咒,在大型項(xiàng)目中哮翘,這都導(dǎo)致非常可怕的維護(hù)問題毛秘。那么饭寺,到底該怎么辦

1.4 接口

“這不就相當(dāng)于讓我根據(jù)用戶手機(jī)殼顏色換主題嗎?”叫挟,可憐的Joe在快被想要沖上去打產(chǎn)品經(jīng)理的時(shí)候(皮一下艰匙。。抹恳。)员凝。腦子突然想起一件神器,他決定試試奋献。他的方法就是使用接口(Interface)健霹,將fly分離出來,放進(jìn)一個(gè)Flyable接口中瓶蚂。針對(duì)于quark方法糖埋,也可以這樣分離進(jìn)Quarkable接口中,

接口

雖然這樣看起來滿足了我們之前所提到的所有需求窃这,但是這樣一來重復(fù)代碼量會(huì)非惩穑可怕,如果有上萬(wàn)個(gè)Duck子類杭攻,Joe一定會(huì)發(fā)瘋的祟敛。所有,這個(gè)方法雖然看起來很好兆解,但是一旦某個(gè)方法或者行為發(fā)生改變垒棋,我們需要定位到所有實(shí)現(xiàn)該方法的類中去修改對(duì)應(yīng)的代碼,這很容易導(dǎo)致bug的發(fā)生痪宰。

那么,到底有沒有一種能夠建立軟件的有效方法畔裕,能夠讓我們可以對(duì)既有的項(xiàng)目在影響最小的情況下修改他的業(yè)務(wù)邏輯衣撬,這樣我們能夠花很少的時(shí)間去修改代碼。

2.策略模式

第一設(shè)計(jì)原則:
找出應(yīng)用中可能需要變化之處扮饶,把它們獨(dú)立出來具练,不要和那些不需要變化的代碼混合在一起。

按照以上的原則進(jìn)行設(shè)計(jì)甜无,代碼發(fā)生變化引起的后果會(huì)非常小扛点,整個(gè)項(xiàng)目會(huì)特別具有彈性哥遮。
這個(gè)原則不僅僅適用于策略模式,對(duì)于之后講解的模式同樣也是核心的指導(dǎo)方向陵究。那么我們繼續(xù)Joe所遇到的Duck問題眠饮。

2.1分開變化的和不會(huì)變化的

我們很清楚,Duck類內(nèi)部的fly()和quark()伴隨著鴨子的不同會(huì)發(fā)生改變铜邮,其他模塊是不變的仪召。為了要把這兩個(gè)變化的行為從Duck類中分開,我們把它們從Duck類中抽離出來松蒜,建議一組新類用來代表每一個(gè)行為扔茅。


Duck可變不可變分析

那么,問題來了秸苗。如何設(shè)計(jì)那組實(shí)現(xiàn)飛行行為和呱呱叫行為的一組類呢召娜?
這里就需要提及第二設(shè)計(jì)原則

第二設(shè)計(jì)原則:
針對(duì)于接口編程,不針對(duì)實(shí)現(xiàn)編程

這里我們希望的是在創(chuàng)建具體的Duck類的時(shí)候惊楼,可以動(dòng)態(tài)的生成對(duì)應(yīng)的行為玖瘸。打個(gè)比方,我們想要產(chǎn)生一個(gè)新的綠頭鴨胁后,將制定類型的飛行行為賦予給它店读。這就說明,在Duck類中攀芯,我們需要包含定義Duck行為的方法屯断,這樣在運(yùn)行的時(shí)候,我們就可以動(dòng)態(tài)去改變綠頭鴨的行為侣诺。
所以這里我們使用兩個(gè)接口來代表兩個(gè)行為殖演,這里定義為 FlyBehavior和QuarkBehavior,行為的每次實(shí)現(xiàn)年鸳,都將實(shí)現(xiàn)對(duì)應(yīng)的接口趴久。

但是接口類是沒有方法體的,也就是說搔确,我們需要一組類實(shí)現(xiàn)對(duì)應(yīng)的行為彼棍,這些專用來實(shí)現(xiàn)類似FlyBehavior和QuarkBehavior的一組類,我們稱為行為類膳算。

這里提到的接口類并非嚴(yán)格意義上Java中的接口(Interface)座硕,可以理解為抽象類或接口。這里我們可以理解為:

"針對(duì)接口編程"真正的意思是“針對(duì)超類型(supertype)編程”

這里講得有點(diǎn)難以理解涕蜂,我們對(duì)比一下針對(duì)實(shí)現(xiàn)編程和針對(duì)接口編程的區(qū)別:

//針對(duì)實(shí)現(xiàn)編程
Dog dog=new Dog();
dog.bark();

//針對(duì)接口編程
Animal animal=new Dog();
animal.makeSoud();

2.2實(shí)現(xiàn)鴨子的行為(代碼)

從上面的講解华匾,我們可以理解可變部分是fly和quack兩種方法。不可變?yōu)镈uck類机隙。那么這里我們需要使用兩個(gè)接口FlyBehavior和QuackBehavior蜘拉,還有一些列他們對(duì)應(yīng)的行為類萨西,具體的結(jié)構(gòu)邏輯如下圖:


鴨子行為

這設(shè)計(jì)有兩個(gè)很明顯的優(yōu)勢(shì):

  • 可以讓飛行和呱呱的叫的動(dòng)作可以被其他對(duì)象復(fù)用,因?yàn)檫@些行為已經(jīng)與鴨子類無關(guān)旭旭。也就是解耦

  • 我們能夠在不影響原有的行為類的情況下新增一些行為谎脯。也就是具備了彈性可拓展性

拓展幾個(gè)概念:

耦合指的就是兩個(gè)類之間的聯(lián)系的緊密程度
解耦指的是解除類之間的直接關(guān)系,將直接關(guān)系轉(zhuǎn)換成間接關(guān)系
想要了解的可以參考這篇文章:https://blog.csdn.net/qq_24499615/article/details/77821896
接下來分別將FlyBehavior,FlyWithWings,FlyNoWay分別貼下

public interface FlyBehavior {

    //飛行
    public void fly();
}
public class FlyWithWings implements FlyBehavior{

    public void fly() {
      System.out.println("I am flying!");  
    }

}
public class FlyNoWay implements FlyBehavior {

    public void fly() {
       System.out.println("I can't fly!"); 

    }

}

接下來將QuackBehavior您机,Quack穿肄,MuteQuack,Squeak類的代碼分別貼下:

public interface QuackBehavior {
    //呱呱叫
    public void quack();
}
public class Quack implements QuackBehavior {

    public void quack() {
       System.out.println("Quack");

    }

}
public class MuteQuack implements QuackBehavior {

    public void quack() {
        System.out.println("<< Slience>>");

    }

}
public class Squeak implements QuackBehavior {

    public void quack() {
       System.out.println("Squeak");
    }

}

到這里將fly和quack的接口類和行為類完成际看。

2.3組合鴨子行為

在前面2.2我們將飛行(fly)和呱呱叫(quack)的動(dòng)作"委托"(delegate)給其他接口類處理咸产,而并非在Duck類(或者子類)中定于fly和quack方法。那么到底該怎么把行為組合進(jìn)Duck中仲闽?

  • 1.首先在Duck類中增加兩個(gè)“實(shí)例變量”脑溢,分別為flyBehavior和quackBehavior,聲明為接口類型(注意不是具體類的實(shí)現(xiàn)類型)赖欣,每個(gè)Duck(或其子類)會(huì)動(dòng)態(tài)的設(shè)置這些變量以在運(yùn)行時(shí)引用正確的行為類型(如FlyWithWings屑彻,Squeak等)。Duck類的類結(jié)構(gòu)如下


    Duck類結(jié)構(gòu)
  • 2.那么顶吮,就開始實(shí)現(xiàn)Duck類

public abstract class Duck {
    //為行為接口類型聲明兩個(gè)引用變量社牲,所有的鴨子(或子類)都繼承它們
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;
    
    public Duck(){
        
    }
    
    public abstract void display();
    
    public void performQuck(){
        //委托給行為處理
        quackBehavior.quack();
    }
    
    public void performFly(){
      //委托給行為處理
        flyBehavior.fly();
    }
    
    public void swim(){
        System.out.println("All ducks float");
    }
}

然后我們實(shí)現(xiàn)一個(gè)MallardDuck類來實(shí)現(xiàn)組合,

public class MallardDuck extends Duck{
    
    public MallardDuck(){
        //使用FlyWithWings作為其FlyBehavior類型
        flyBehavior=new FlyWithWings();
        //綠頭鴨使用Quck類處理呱呱叫悴了,
        //所以當(dāng)performQuack被調(diào)用時(shí)搏恤,叫這個(gè)行為被委托給Quck對(duì)象
        quackBehavior=new Quack();
    }
    
    /*
     * 因?yàn)镸allardDuck繼承自Duck類
     * ,所以具備flyBehavior與quackBehavior實(shí)例變量
     */

    public void display() {
        // TODO Auto-generated method stub
        
    }
}

當(dāng)然構(gòu)造器內(nèi)還是需要實(shí)現(xiàn)具體行為類湃交,這在之后的模式中會(huì)提供相應(yīng)的解決方案熟空,之后我們會(huì)回歸到這個(gè)問題繼續(xù)解決這個(gè)問題。

到這里搞莺,組合鴨子類已經(jīng)實(shí)現(xiàn)息罗。

  • 3.測(cè)試效果
    這里我們編譯測(cè)試類
public class MiniDuckSimilator {
    
    public static void main(String[] args) {
        Duck mallerdDuck=new MallardDuck();
        //一下代碼是將具體的行為委托給對(duì)應(yīng)的行為類處理行為
        mallerdDuck.performQuck();
        mallerdDuck.performFly();
                
    }

}
運(yùn)行結(jié)果

2.4 動(dòng)態(tài)行為設(shè)定

在之前的實(shí)現(xiàn)中我們是在Duck的具體子類中實(shí)現(xiàn)FlyBehavior和QuackBehavior的行為,但是Duck失去了動(dòng)態(tài)設(shè)定的功能才沧,對(duì)于追求完美的程序員來說是不可饒恕的迈喉。所以急切需要通過一個(gè)方法動(dòng)態(tài)設(shè)定行為,而并非是在鴨子(Duck)的構(gòu)造器中去實(shí)例化温圆。這里推薦一個(gè)方法-----設(shè)定方法(setter method)

    1. 在Duck類中增加兩個(gè)新方法 setFlyBehavior()和setQuckBehavior().對(duì)于Duck的類結(jié)構(gòu)修改如下


      image.png

      具體修改如下

public abstract class Duck {
    //為行為接口類型聲明兩個(gè)引用變量挨摸,所有的鴨子(或子類)都繼承它們
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;
    
    public Duck(){
        
    }
    
    public abstract void display();
    
    public void performQuck(){
        //委托給行為處理
        quackBehavior.quack();
    }
    
    public void performFly(){
      //委托給行為處理
        flyBehavior.fly();
    }
    
    public void setFlyBehavior(FlyBehavior flyBehavior){
        this.flyBehavior=flyBehavior;
    }
    
    public void setQuackBehavior(QuackBehavior quackBehavior){
        this.quackBehavior=quackBehavior;
    }
    
    public void swim(){
        System.out.println("All ducks float");
    }
}
  • 2.創(chuàng)建一個(gè)新的鴨子模型:模型鴨(ModelDuck)
public class ModelDuck extends Duck{
    
    public ModelDuck(){
        flyBehavior=new FlyNoWay();
        quackBehavior=new Quack();
    }

    public void display() {
       System.out.println("I'm a model duck");
        
    }

}
  • 3.新建立一個(gè)新的FlyBehavior類型 FlyRocketPowered
public class FlyRocketPowered implements FlyBehavior{

    public void fly() {
      System.out.println("I'm flying with a rocket!");
        
    }

}
  • 4.修改測(cè)試類MiniDuckSimulator,加上模型鴨捌木,并令模型鴨具備火箭動(dòng)力
public class MiniDuckSimilator {
    
    public static void main(String[] args) {
        Duck mallerdDuck=new MallardDuck();
        //一下代碼是將具體的行為委托給對(duì)應(yīng)的行為類處理行為
        mallerdDuck.performQuck();
        mallerdDuck.performFly();
        
        Duck modelDuck=new ModelDuck();
        //第一次會(huì)使用構(gòu)造參數(shù)里的飛行模式
        modelDuck.performFly();
        modelDuck.setFlyBehavior(new FlyRocketPowered());
        //模型鴨具備火箭飛行能力
        modelDuck.performFly();
                
    }

}

運(yùn)行結(jié)果:


運(yùn)行結(jié)果

到這里我們發(fā)現(xiàn)鴨子模型中我們使用到類的組合使用,而這里我們涉及到第三個(gè)設(shè)計(jì)原則:

第三個(gè)設(shè)計(jì)原則:
多用組合嫉戚,少用繼承

正如我們所見刨裆,組合所建立的系統(tǒng)具備極大的彈性澈圈,不僅僅可以將行為封裝為一系列的行為類,更可以動(dòng)態(tài)改變行為帆啃,只需要組合的行為對(duì)象是符合正確的行為接口標(biāo)準(zhǔn)的瞬女。

3.策略模式講解

總結(jié)之前的三個(gè)設(shè)計(jì)原則:

第一設(shè)計(jì)原則
找出應(yīng)用中可能需要變化之處,把它們獨(dú)立出來努潘,不要和那些不需要變化的代碼混合在一起诽偷。

第二設(shè)計(jì)原則
針對(duì)于接口編程,不針對(duì)實(shí)現(xiàn)編程

第三設(shè)計(jì)原則
多用組合疯坤,少用繼承

總結(jié)這三條原則結(jié)合起來就是我們學(xué)習(xí)的第一個(gè)模式:

策略模式 定義了算法族报慕,分別封裝起來,讓它們之間可以相互替換压怠,此模式讓算法的變化獨(dú)立于使用算法的客戶眠冈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市菌瘫,隨后出現(xiàn)的幾起案子蜗顽,更是在濱河造成了極大的恐慌,老刑警劉巖雨让,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雇盖,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡栖忠,警方通過查閱死者的電腦和手機(jī)崔挖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娃闲,“玉大人虚汛,你說我怎么就攤上這事』拾铮” “怎么了卷哩?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)属拾。 經(jīng)常有香客問我将谊,道長(zhǎng),這世上最難降的妖魔是什么渐白? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任尊浓,我火速辦了婚禮,結(jié)果婚禮上纯衍,老公的妹妹穿的比我還像新娘栋齿。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布瓦堵。 她就那樣靜靜地躺著基协,像睡著了一般。 火紅的嫁衣襯著肌膚如雪菇用。 梳的紋絲不亂的頭發(fā)上澜驮,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音惋鸥,去河邊找鬼杂穷。 笑死,一個(gè)胖子當(dāng)著我的面吹牛卦绣,可吹牛的內(nèi)容都是我干的耐量。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼迎卤,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼拴鸵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蜗搔,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤劲藐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后樟凄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聘芜,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年缝龄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了复罐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啼染。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡酝惧,死狀恐怖沈善,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情炼绘,我是刑警寧澤嗅战,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站俺亮,受9級(jí)特大地震影響驮捍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜脚曾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一东且、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧本讥,春花似錦珊泳、人聲如沸鲁冯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)晓褪。三九已至,卻和暖如春综慎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背勤庐。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工示惊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人愉镰。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓米罚,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親丈探。 傳聞我的和親對(duì)象是個(gè)殘疾皇子录择,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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