策略模式

需求

展示一只鴨子,鴨子會(huì)叫,會(huì)游泳,還有不同的外觀。

初步設(shè)計(jì)

public abstract class Duck
{
    /**
     * 所有的鴨子都會(huì)有外觀,只是每只鴨子的外觀都可能不一樣
     */
    public abstract void display();

    public void swim()
    {
        System.out.println("鴨子會(huì)游泳");
    }

    public void quack()
    {
        System.out.println("鴨子會(huì)呱呱叫");
    }
}

看起來(lái)似乎不錯(cuò),?鴨子只要繼承自 Duck,然后實(shí)現(xiàn)自己的外觀即可了。

一段時(shí)間后,需求改了,現(xiàn)在需要鴨子會(huì)飛,叫聲也可能有不一樣的

于是就在父類(lèi)中加了 fly 的方法

public abstract class Duck
{
    public void fly()
    {
        System.out.println("讓鴨子飛");
    }

    // 省略其他的
}

叫聲在這里不用修改,因?yàn)樽宇?lèi)只需要覆蓋quack方法即可。

然后可怕的事情發(fā)生了,現(xiàn)在發(fā)現(xiàn),橡皮鴨子也會(huì)飛了,原本是不應(yīng)該會(huì)飛的。

可見(jiàn)為了復(fù)用而使用繼承會(huì)導(dǎo)致問(wèn)題:原本某些子類(lèi)不需要的能力被父類(lèi)強(qiáng)行賦予了

思考以上代碼會(huì)導(dǎo)致的問(wèn)題:

  1. 代碼在多個(gè)子類(lèi)中重復(fù)(不會(huì)飛的鴨子也有了飛的方法)
  2. 牽一發(fā)動(dòng)全身,后續(xù)需要加入新功能,會(huì)導(dǎo)致所有的子類(lèi)都受影響
  3. 不能動(dòng)態(tài)改變鴨子的行為,比如讓他豎著飛或橫著飛,也很難知道鴨子的全部行為

既然繼承?不好,那就使用接口怎么樣?

考慮到“飛”,“叫聲”行為是?可變的,因此封裝成接口,讓需要飛和叫的鴨子實(shí)現(xiàn)這些接口不就好了嗎?來(lái)試試!

/**
 * 所有飛行行為類(lèi)必須實(shí)現(xiàn)的接口
 */
public interface FlyBehavior
{
    public void fly();
}

/**
 * 所有鴨叫行為必須實(shí)現(xiàn)的接口
 */
public interface QuackBehavior
{
    public void quack();
}

那么鴨子父類(lèi)就變成:

public abstract class Duck
{
    /**
     * 所有的鴨子都會(huì)有外觀,只是每只鴨子的外觀都可能不一樣
     */
    public abstract void display();

    public void swim()
    {
        System.out.println("鴨子會(huì)游泳");
    }
}

而需要叫聲或者飛行行為的鴨子之類(lèi),就可以通過(guò)實(shí)現(xiàn)接口來(lái)完成,然后實(shí)現(xiàn)自己的飛和叫的行為,不管是橫著飛豎著飛,你自己都可以決定。

但是也會(huì)導(dǎo)致以下問(wèn)題:

  • 如果需要修改行為,那么就要具體到每個(gè)鴨子子類(lèi)的源碼,一不小心可能就會(huì)出現(xiàn)問(wèn)題。
  • 代碼無(wú)法復(fù)用,比如說(shuō),定義了 A 鴨子會(huì)?橫著飛,,B 鴨子會(huì)橫著飛,那么橫著飛這個(gè)行為就會(huì)在 A 和 B 中重復(fù)。

軟件開(kāi)發(fā)的真理

需求永遠(yuǎn)在變

設(shè)計(jì)原則 1

找出可能變化的地方,并將可能會(huì)變化的獨(dú)立出來(lái),不要和不變的那些?代碼混在一起。
也就是說(shuō):如果有新需求?來(lái)了,就會(huì)使某一塊變化,那么就可以確定,這塊是需要被?抽出來(lái)的。確保系統(tǒng)中某一部分的改變,不會(huì)導(dǎo)致影響到其他部分

設(shè)計(jì)原則 2

針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程,是針對(duì)超類(lèi)型編程,這里的接口不一定是語(yǔ)法意義上的接口。也就是利用多態(tài)。

OK ?,現(xiàn)在可以將飛和叫聲這兩個(gè)會(huì)變化的獨(dú)立出來(lái)了

我們可以將飛這個(gè)行為歸為一組,將叫聲這個(gè)行為歸為一組。它們將和鴨子類(lèi)完全隔離。

/**
 * 飛行行為的實(shí)現(xiàn)類(lèi),給不會(huì)飛的鴨子用
 */
public class FlyNoWay implements FlyBehavior
{
    @Override
    public void fly()
    {
        System.out.println("我不會(huì)飛");
    }
}

/**
 * 飛行實(shí)現(xiàn)類(lèi),用火箭來(lái)飛
 */
public class FlyRocketPowered implements FlyBehavior
{
    @Override
    public void fly()
    {
        System.out.println("我可以和火箭一起飛");
    }
}

/**
 * 飛行行為的實(shí)現(xiàn),給真會(huì)飛的鴨子用
 */
public class FlyWithWings implements FlyBehavior
{

    @Override
    public void fly()
    {
        System.out.println("我會(huì)飛");
    }
}
/**
 * 聲音實(shí)現(xiàn)類(lèi),給會(huì)吱吱叫的鴨子用
 */
public class Squeak implements QuackBehavior
{
    @Override
    public void quack()
    {
        System.out.println("我會(huì)吱吱叫");
    }
}

/**
 * 叫聲實(shí)現(xiàn)類(lèi),給不會(huì)叫的鴨子用
 */
public class MuteQuack implements QuackBehavior
{
    @Override
    public void quack()
    {
        System.out.println("我不會(huì)叫");
    }
}

整合鴨子的行為

?如何?讓鴨子和他們的行為發(fā)生關(guān)聯(lián)呢?我們的目的是行為可以被動(dòng)態(tài)賦予,因此可以讓行為成為鴨子的一個(gè)實(shí)例變量,暴露出相應(yīng)的方法去觸發(fā)行為。新的鴨子類(lèi)如下:

/**
 * 鴨子父類(lèi)
 */
public abstract class Duck
{
    // 所有鴨子子類(lèi)都實(shí)現(xiàn)這兩個(gè)接口類(lèi)型
    protected FlyBehavior flyBehavior;
    protected QuackBehavior quackBehavior;

    /**
     * 所有的鴨子都會(huì)有外觀,只是每只鴨子的外觀都可能不一樣
     */
    public abstract void display();

    // 委托給飛行行為類(lèi)
    public void performFly()
    {
        flyBehavior.fly();
    }

    // 委托給鴨叫聲類(lèi)
    public void perfirmQuack()
    {
        quackBehavior.quack();
    }

    /**
     * 只要是鴨子都會(huì)游泳
     */
    public void swim()
    {
        System.out.println("所有的鴨子都會(huì)游泳");
    }

    /**
     * 動(dòng)態(tài)設(shè)定鴨子的飛行方式
     */
    public void setFlyBehavior(FlyBehavior fb)
    {
        flyBehavior = fb;
    }

    /**
     * 動(dòng)態(tài)設(shè)定鴨子的叫聲
     */
    public void setQuackBehavior(QuackBehavior qb)
    {
        quackBehavior = qb;
    }
}

測(cè)試

現(xiàn)在可以創(chuàng)建你想要的鴨子了。

/**
 * 綠頭鴨類(lèi),一繼承了Duck類(lèi),從一開(kāi)始就是會(huì)游泳的,而外觀可以自己定義,飛行方式和叫聲都可以根據(jù)需要自己定義
 */
public class MallardDuck extends Duck
{
    public MallardDuck()
    {
        // 聲明這個(gè)鴨子是飛行和叫聲行為
        flyBehavior = new FlyWithWings();
        quackBehavior = new Quack();
    }

    @Override
    public void display()
    {
        System.out.println("我是一只綠頭鴨");
    }
}
/**
 * 創(chuàng)建一個(gè)模型鴨,一開(kāi)始是不會(huì)飛的
 */
public class ModelDuck extends Duck
{
    public ModelDuck()
    {
        flyBehavior = new FlyNoWay();
        quackBehavior = new Quack();
    }

    @Override
    public void display()
    {
        System.out.println("我是一只模型鴨");
    }
}
public static void main(String[] args)
{
    // 創(chuàng)建一個(gè)綠頭鴨
    Duck mallardDuck = new MallardDuck();
    // 調(diào)用繼承而來(lái)的performFly方法,委托給FlyBehavior對(duì)象處理
    mallardDuck.performFly();
    // 調(diào)用繼承而來(lái)的performQuack方法,委托給QuackBehavior對(duì)象處理
    mallardDuck.perfirmQuack();

    // 創(chuàng)建一個(gè)模型鴨
    Duck modelDuck = new ModelDuck();
    modelDuck.performFly();
    modelDuck.perfirmQuack();
    // 動(dòng)態(tài)設(shè)定鴨子的飛行方式,這里讓鴨子和火箭一起飛
    modelDuck.setFlyBehavior(new FlyRocketPowered());
    // 這樣就可以動(dòng)態(tài)的設(shè)定鴨子的行為了,如果綁定在鴨子類(lèi)中,就無(wú)法這樣做
    modelDuck.performFly();
}

設(shè)計(jì)原則 3

多用組合,少用繼承。

上面可以看到的是,鴨子的行為并不是通過(guò)繼承而來(lái)的,而是通過(guò)組合而來(lái)的。

鴨子的飛行行為可以看做是一族算法,叫聲行為也可以看做是一族算法,在一族算法內(nèi),族內(nèi)的行為是可以互相替換的,比如一族飛行算法,橫著飛和豎著飛是可以互相替換的。

策略模式的定義:

定義了算法族,分別封裝起來(lái),讓他們之間可以互相替換,此模式讓算法的變化獨(dú)立于使用算法的客戶(hù)。

最終代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 模擬鴨子游戲的需求 SimUDuck游戲中會(huì)出現(xiàn)各種鴨子,一邊游泳戲水,一邊呱呱叫。通過(guò)標(biāo)準(zhǔn)的OO技術(shù),設(shè)計(jì)一個(gè)超...
    一縷陽(yáng)憶往昔閱讀 557評(píng)論 2 0
  • 設(shè)計(jì)模式 開(kāi)題先說(shuō)明一下,設(shè)計(jì)模式告訴我們?nèi)绾谓M織類(lèi)和對(duì)象以解決某種問(wèn)題。讓代碼變得更加優(yōu)雅是我們責(zé)無(wú)旁貸的任務(wù) ...
    tanghuailong閱讀 491評(píng)論 0 2
  • 定義 定義了算法族,分別封裝起來(lái),讓它們之間可以互相替換,此模式讓算法的變化獨(dú)立于使用算法的客戶(hù)。 Define ...
    狐尼克朱迪閱讀 291評(píng)論 0 0
  • 設(shè)計(jì)模式入門(mén) 設(shè)計(jì)模式是人們?cè)诿鎸?duì)同類(lèi)型軟件工程設(shè)計(jì)問(wèn)題所總結(jié)出來(lái)的一些有用的經(jīng)驗(yàn)。模式不是代碼,而是某類(lèi)問(wèn)題的通...
    在南方的北方人_Elijah閱讀 370評(píng)論 0 3
  • 本文解決問(wèn)題 什么是策略模式? 策略模式的優(yōu)缺點(diǎn)以及策略模式解決了什么痛點(diǎn) 策略模式的適用環(huán)境 什么是策略模式? ...
    慕久久閱讀 684評(píng)論 0 1

友情鏈接更多精彩內(nèi)容