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

模擬鴨子游戲的需求

SimUDuck
游戲中會出現(xiàn)各種鴨子,一邊游泳戲水,一邊呱呱叫。
通過標(biāo)準(zhǔn)的OO技術(shù),設(shè)計(jì)一個(gè)超類。

策略模式01

需求增加

此程序需要會飛 的鴨子競爭者拋在后頭

方法一:在 Duck 超類加上 fly() 方法

但問題發(fā)生了:并非 Duck 所有的子類都會飛(玩具鴨子)

注意:當(dāng)涉及到維護(hù)是,為了“復(fù)用”(reuse)目的而使用繼承,結(jié)局并不完美

方法二:覆蓋超類的方法

當(dāng)有新的鴨子出現(xiàn),要檢查每個(gè)鴨子可能要覆蓋 fly() 和 quark() ...

  • 利用繼承來提供行為,導(dǎo)致的缺點(diǎn)
    1. 代碼在多個(gè)子類中重復(fù)
    2. 運(yùn)行時(shí)的行為不容易改變
    3. 很難知道所有鴨子的全部行為
    4. 改變引發(fā)全身,造成其他鴨子不想要的改變

方法三:利用接口

可以把 fly() 從超類中提取出來,放進(jìn)一個(gè)“Flyable接口”中。這么一來,只有會飛的鴨子才實(shí)現(xiàn)此接口。
看似不錯(cuò)(只適應(yīng)用不多,且飛的方式不同的情況),但是在會飛的鴨子種類很多后,要稍微修改一下飛行的行為,就是一個(gè)災(zāi)難...

分析

  1. 不是所有的子類都具有飛行和呱呱叫的行為,所以繼承不合適。
  2. 雖然 Flyable 和 Quackable 可以解決“一部分”問題,但是卻造成代碼無法復(fù)用,這只是從一個(gè)坑進(jìn)入另一個(gè)坑。
  3. 甚至,在會飛的鴨子中,飛行動作可能還有多種變化。。。

解決之道--“采用良好的OO軟件設(shè)計(jì)原則”

軟件開發(fā)的一個(gè)不變的真理 --> 需求和改變

第一個(gè)設(shè)計(jì)原則:找出應(yīng)用中可能需要變化之處,把它們獨(dú)立出來,不要和那些不需要變化的代碼混在一起
把會變化的部分取出來并“封裝”起來,好讓其它部分不會受到影響

為了把這兩個(gè)行為從Duck 類中分開,將它們從 Duck 類中取出來,建立一組新類來代表每個(gè)行為。

設(shè)計(jì)鴨子的行為

讓鴨子類中的行為可以動態(tài)的改變就好了。我們應(yīng)該在鴨子類中包含設(shè)定行為的方法,這樣就可以在“運(yùn)行時(shí)”動態(tài)的“改變”飛行行為。

第二個(gè)設(shè)計(jì)原則:針對接口編程,而不是針對實(shí)現(xiàn)編程
針對超類型編程

利用接口代表每個(gè)行為,比方說 FiyBehavior 和 QuackBehavior ,而行為的每個(gè)實(shí)現(xiàn)都將實(shí)現(xiàn)其中的一個(gè)接口。由行為類而不是Duck類來實(shí)現(xiàn)接口。


  • 以前的做法:行為來自 Duck 超類的具體實(shí)現(xiàn),或是繼承某個(gè)接口并由子類自行實(shí)現(xiàn)而來。都依賴于“實(shí)現(xiàn)”,被實(shí)現(xiàn)綁的死死的,沒辦法更改行為(除非寫更多代碼)。

vs

  • 新設(shè)計(jì)中:鴨子的子類將使用接口(FlyBehavior 與 QuackBehavior)所標(biāo)示的行為,實(shí)際的“實(shí)現(xiàn)”不會被綁死在鴨子的子類中。即特定的具體行為編寫在實(shí)現(xiàn)了FlyBehavior 與 QuackBehavior 的子類中。

策略模式02

這樣的設(shè)計(jì),可以放飛行和呱呱叫的動作被其他的對象復(fù)用,因?yàn)檫@些行為已經(jīng)與鴨子類無關(guān)了。
而我們可以新增一些行為,不會影響到既有的行為類,也不會影響“使用”到飛行行為的鴨子類。

整合鴨子的行為

關(guān)鍵在于:鴨子現(xiàn)在會將飛行和呱呱叫的動作“委托”(delegate)別人處理,而不是使用定義在 Duck 類(或子類)內(nèi)的呱呱叫和飛行方法。
做法:

  1. 首先,在 Duck 類中加入“兩個(gè)實(shí)例變量”,分別為“flyBehavior”與“quackBehavior”,聲明為接口類型(而不是具體類實(shí)現(xiàn)類型),每個(gè)鴨子的對象都會動態(tài)地設(shè)置這些變量以在運(yùn)行時(shí)引用正確的行為類型。
策略模式02
  1. 實(shí)現(xiàn) performQuack();
public class Duck{
    QuackBehavior quackBehavior;
    // 還有更多
    public void performQuack(){
        quackBehavior.quack();//鴨子對象不親自處理呱呱叫行為,而是委托給quackBehavior引用的對象。
    }
}
  1. 如何設(shè)定 flyBehavior 與 quackBehavior的實(shí)例變量
public class MallardDuck extends Duck{
    public MallardDuck(){
        quackBehavior = new  Quack();
        flyBehavior = new FlyWithWings();
    }
    //別忘了,因?yàn)?MallarDuck 繼承了 Duck 類,所以具有 flyBehavior 與 quackBehavior 實(shí)例變量
}

我們正在構(gòu)造器里面制造了具體的Quack實(shí)現(xiàn)類的實(shí)例(在后面的模式中,可以修正這一點(diǎn))

動態(tài)設(shè)定行為

通過 set 方法來設(shè)定鴨子的行為,而不是在鴨子的構(gòu)造器內(nèi)實(shí)例化。
Duck 類中 加入 setFlyBehavior(FlyBehavior fly);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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