設計模式之策略模式

策略模式的定義

  • 策略模式(Strategy Pattern):定義一系列算法,將每一個算法封裝起來,并讓它們可以相互替換。策略模式讓算法獨立于使用它的客戶而變化,也稱為政策模式(Polic
    y)。

策略模式是一種對象"行為型"模式。

策略模式的使用場景

  • 針對同一類型問題的多種處理方式,僅僅是具體行為有差別時。
  • 需要安全的封裝多種同一類型的操作時。
  • 出現(xiàn)同一抽象類有多個子類,而又需要使用if-else 或者 switch-case 來選擇具體子類時。

策略模式的UML類圖

策略模式UML類圖1-0.png

角色介紹:

  • Context : 用來操作策略上下文環(huán)境。
  • Stragety : 策略的抽象。
  • ConcreteStragetyA , ConcreteStragetyB : 具體的策略實現(xiàn)。

策略模式示例:

  • 假設有一個模擬鴨子的游戲,游戲中會出現(xiàn)各種鴨子,一邊游泳戲水,一邊呱呱叫。這個游戲的內(nèi)部設計了一個鴨子超類Duck,并讓各種鴨子繼承此超類。
    圖1-0.png

    讓鴨子能飛
    去年,公司的競爭力加劇,公司主管認為該是創(chuàng)新的時候了。主管認為,此模擬程序需要會飛的鴨子,將競爭者拋在后面。
    圖1-1.png
圖1-2.png
圖1-4.png
圖1-5.png

圖1-6.png

改進繼承
Joe認識到繼承可能不是一個好的解決辦法,因為他剛剛拿到來自主管的備忘錄,希望以后每六個月更新產(chǎn)品(至于更新辦法,他們還沒想到)。Joe知道規(guī)格會常常改變,每當有新的鴨子子類出現(xiàn),他就要被迫檢視并可能需要覆蓋fly()和quack().....這簡直是無窮盡的噩夢。所以,他需要一個更清晰的方法,讓某些(而不是全部)鴨子類型可飛或可叫。
圖1-7.png

圖1-8.png
圖1-9.png

第一個設計原則

設計原則:找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。這個概念很簡單,幾乎是每個設計模式背后的精神所在,所有的模式都提供了一套方法讓系統(tǒng)中的某部分改變不會影響其它部分。

1. 分開變化和不會變化的部分

  • 就我們目前所知,除了fly()和quack()的問題之外,Duck類還算一切正?!,F(xiàn)在,為了要分開“變化和不會變化的部分”,我們準備建立兩組類(完全遠離Duck類),一個是“fly”相關的,一個是“quack”相關的,每一組類將實現(xiàn)各自的動作。比如說,我們可能有一個類實現(xiàn)“呱呱叫”,另一個類實現(xiàn)“嘰嘰叫”,另一個類實現(xiàn)“安靜”。


    分開變化的部分和不變化的部分.png

2. 設計鴨子的行為

  • 如何設計那組實現(xiàn)飛行和呱呱叫的行為的類呢?
    我們希望一切能有彈性,畢竟,正是因為一開始鴨子行為沒有彈性,才讓我們走上現(xiàn)在這條路。我們應該在鴨子類中包含設定行為的方法,這樣可以實現(xiàn)在“運行時”動態(tài)地“改變”鴨子的飛行行為。

第二個設計原則

設計原則:針對接口編程,而不是針對實現(xiàn)編程。

針對接口編程,而不是針對實現(xiàn)編程,舉例:

// 針對實現(xiàn)編程
// 聲明變量“d”為Dog類型,會造成我們必須針對具體實現(xiàn)編碼
Dog d = new Dog();
d.bark();

// 針對接口/超類型編程
// 我們知道該對象是狗,但是我們現(xiàn)在利用animal進行多態(tài)的調(diào)用
Animal animal = new Dog();
animal.makeSound();

// 更棒的是,子類實例化的動作不再需要在代碼中硬編碼,
// 例如new Dog(),而是“在運行時才指定具體實現(xiàn)的對象”
// 我們不知道實際的子類型是“什么”,我們只關心它知道如何正確地進行makeSound()的動作就夠了
a = getAnimal();
a.makeSound();
針對接口編程.png

所以我們可以利用接口代表行為,比方說,F(xiàn)lyBehavior與QuackBehavior,而行為的每個實現(xiàn)都將實現(xiàn)其中一個接口。
所以這次鴨子類不會負責實現(xiàn)Flying與Quacking接口,反而是由我們制造一組其他類專門實現(xiàn)FlyBehavior與QuackBehavior,這就成為“行為”類。

設計鴨子的行為.png

這樣的做法迥異于以往,以前的做法是:行為來自Duck超類的具體實現(xiàn),或是繼承某個接口并由子類自行實現(xiàn)。這兩種做法都是依賴于“實現(xiàn)”,我們被“實現(xiàn)”綁得死死的,沒辦法更改行為。
在我們的新設計中,鴨子的子類將使用接口(FlyBehavior與QuackBehavior)所表示的行為,所以實際的“實現(xiàn)”不會被綁死在鴨子的子類中。

3.實現(xiàn)鴨子的行為

實現(xiàn)鴨子的行為.png

這樣的設計,可以讓飛行和呱呱叫的動作被其他的對象復用,因為這些行為已經(jīng)與鴨子無關了。而我們可以新增一些行為,不會影響到既有的行為類,也不會影響有使用到飛行行為的鴨子類。

4. 集成鴨子的行為

  • 鴨子現(xiàn)在會將飛行和呱呱叫的動作,委托(delegate)別人處理,而不是使用定義在自己類(或子類)內(nèi)的方法。
    1. 首先,在鴨子中加入兩個實例變量, 分別為FlyBehavior與QuackBehavior,聲明為接口類型(而不是具體類實現(xiàn)類型),每個變量會利用多態(tài)的方式在運行時引用正確的行為類型(例如:FlyWithWings、Squeak . . . 等)。
    2. 我們也必須將Duck類與其所有子類中的fly() 與quack( ) 移除,因為這些行為已經(jīng)被搬移到FlyBehavior與QuackBehavior類中了。我們用performFly()和performQuack()取代Duck類中的fly()與quack()。
      每一個鴨子都有一個FlyBehavior和QuackBehavior,好將飛行和呱呱叫委托給它們代為處理。


      集成鴨子的行為.png

5.動態(tài)的設定行為
在Duck類中,加入兩個新方法:

 public void setFlyBehavior(FlyBehavior fb){
        flyBehavior=fb;
 }
  public void setQuackBehavior(QuackBehavior qb){
        quackBehavior=qb;
 }

從此以后,我們可以“隨時”調(diào)用這兩個方法改變鴨子的行為。

代碼示例:

Duck.java

/**
 * 抽象鴨子超類
 */
public abstract class Duck {
    protected FlyBehavior flyBehavior;
    protected QuackBehavior quackBehavior;

    public Duck() {

    }

    public abstract void display();

    public void swim() {
        System.out.println("All ducks float, even decoys!");
    }

    public void performFly() {
        flyBehavior.fly();
    }

    public void performQuack() {
        quackBehavior.quack();
    }

    public void setFlyBehavior(FlyBehavior fb) {
        flyBehavior = fb;
    }

    public void setQuackBehavior(QuackBehavior qb) {
        quackBehavior = qb;
    }

}

DecoyDuck.java

/**
 * 誘餌鴨
 */
public class DecoyDuck extends Duck {

    public DecoyDuck() {
        setFlyBehavior(new FlyNoWay());
        setQuackBehavior(new MuteQuack());
    }

    @Override
    public void display() {
        System.out.print("I'm a duck Decoy");
    }

}

ModelDuck.java

/**
 * 模型鴨
 */
public class ModelDuck extends Duck {
    public ModelDuck() {
        flyBehavior = new FlyNoWay();
        quackBehavior = new Quack();
    }

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

MallardDuck.java

/**
 * 綠頭鴨
 */
public class MallardDuck extends Duck {

    public MallardDuck() {
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }

    @Override
    public void display() {
        System.out.println("I'm a real Mallard duck");
    }
}

RubberDuck.java

/**
 * 橡皮鴨
 */
public class RubberDuck extends Duck{
    public RubberDuck() {
        flyBehavior = new FlyNoWay();
        quackBehavior = new Squeak();
    }

    @Override
    public void display() {
        System.out.println("I'm a rubber duckie");
    }
}

RedHeadDuck.java

/**
 * 紅頭鴨
 */
public class RedHeadDuck extends Duck {
    public RedHeadDuck() {
        flyBehavior = new FlyWithWings();
        quackBehavior = new Quack();
    }

    @Override
    public void display() {
        System.out.println("I'm a real Red Headed duck");
    }
}

FlyBehavior.java

/**
 * 飛行行為接口
 */
public interface FlyBehavior {
    void fly();
}

FlyRocketPowered.java

/**
 * 火箭動力飛行
 */
public class FlyRocketPowered implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("I'm flying with a rocket");
    }
}

FlyNoWay.java

/**
 * 不會飛
 */
public class FlyNoWay implements FlyBehavior{
    @Override
    public void fly() {
        System.out.println("I can't fly");
    }
}

FlyWithWings.java

/**
 * 實現(xiàn)鴨子飛行
 */
public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.print("I'm flying!!");
    }
}

QuackBehavior.java

/**
 * 叫的行為接口
 */
public interface QuackBehavior {
    void quack();
}

Quack.java

/**
 * 嘎嘎叫
 */
public class Quack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("Quack");
    }
    
}

FakeQuack.java

/**
 * 假嘎嘎叫
 */
public class FakeQuack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("Qwak");
    }
}

Squeak.java

/**
 * 吱吱叫
 */
public class Squeak implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("Squeak");
    }
}

MuteQuack.java

/**
 * 禁音
 */
public class MuteQuack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("<< Silence >>");
    }
}

測試程序 MiniDuckSimulator.java

/**
 * 迷你鴨子模擬器測試程序
 */
public class MiniDuckSimulator {
    public static void main(String[] args){
        MallardDuck mallard = new MallardDuck();
        RubberDuck rubberDuckie = new RubberDuck();
        DecoyDuck decoy = new DecoyDuck();

        Duck model = new ModelDuck();

        mallard.performQuack();
        rubberDuckie.performQuack();
        decoy.performQuack();

        model.performQuack();
        model.setFlyBehavior(new FlyRocketPowered());
        model.performFly();
    }
}

測試結果:

Quack
Squeak
<< Silence >>
Quack
I'm flying with a rocket

總結

  • 所有鴨子從Duck繼承,飛行行為實現(xiàn)FlyBehavior接口,呱呱叫行為實現(xiàn)QuackBehavior接口。


    總的類圖.png

“有一個”(has a)可能比“是一個”(is a)更好
有一個關系相當有趣:每一鴨子都有一個FlyBehavior且有一個QuackBehavior,讓鴨子將飛行和呱呱叫委托它們代為處理。

如果將兩個類結合起來使用(如同本例),這就是組合(Composition)。這種作法和繼承不同的地方在于:鴨子的行為不是繼承而來,而是和適當?shù)男袨閷ο蠼M合而來。

第三個設計原則

設計原則:

  • 多用組合,少用繼承
  • 使用組合建立系統(tǒng)具有很大的彈性,不僅可將算法族封裝成類,更可以在運行時動態(tài)地改變行為。

優(yōu)點

  • 策略模式主要用來分離算法,在相同的行為抽象下有不同的具體實現(xiàn)策略。這個模式很好的演示了開閉原則,也就是定義抽象,注入不同的實現(xiàn),從而達到很好的可擴展性。
  • 使用策略模式可以避免使用多重條件轉(zhuǎn)移語句。多重轉(zhuǎn)移語句不易維護,它把采取哪一種算法或采取哪一種行為的邏輯與算法或行為的邏輯混合在一起,統(tǒng)統(tǒng)列在一個多重轉(zhuǎn)移語句里面,比使用繼承的辦法還要原始和落后。
  • 結構清晰明了,使用簡單直觀。
  • 耦合度相對而言較低,擴展方便。
  • 操作封裝也更為徹底,數(shù)據(jù)更為安全。

缺點

  • 客戶端必須知道所有的策略類,并自行決定使用哪一個策略類。這就意味著客戶端必須理解這些算法的區(qū)別,以便適時選擇恰當?shù)乃惴?。換言之,策略模式只適用于客戶端知道所有的算法或行為的情況。
  • 隨著策略的增加,策略模式會造成很多的策略類,每個具體策略類都會產(chǎn)生一個新類。有時候可以通過把依賴于環(huán)境的狀態(tài)保存到客戶端里面,而將策略類設計成可共享的,這樣策略類實例可以被不同客戶端使用。換言之,可以使用享元模式來減少對象的數(shù)量。

Android源碼中的策略模式實現(xiàn)

  • 時間插值器

參考資料
《Head First 設計模式》
《Android 源碼設計模式解析與實戰(zhàn)》

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

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

  • 策略模式,是我們接觸到的第一個設計模式,也是較容易理解的一個模式。我們可以給它下一個定義:** 定義了算法族,分別...
    六尺帳篷閱讀 848評論 0 8
  • 一直想把常見的設計模式系統(tǒng)地學習一遍,結果和大多數(shù)人一樣,過了幾天就沒能堅持下去了。我發(fā)現(xiàn)學習這件事情急不得,往往...
    Neulana閱讀 611評論 5 2
  • 思考: 假設有個需求,模擬鴨子游戲:在游戲中會出現(xiàn)各種各樣的鴨子,一邊游泳戲水,一邊呱呱叫。開始我們的設計吧: 這...
    MarksGui閱讀 210評論 0 0
  • 需求: 1:模擬鴨子項目 從項目"模擬鴨子游戲"開始。 鴨子都會叫、會游泳。有的鴨子是紅頭的、有的鴨子是綠頭的。 ...
    凱哥Java閱讀 169評論 0 1
  • http://www.weinisongdu.com/admin/shareOpus/14927251?from=...
    蔣光頭jL94430閱讀 598評論 20 24

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