策略模式(Strategy Pattern):封裝變化,靈活應對需求變更

GitHub源碼分享

微信搜索:碼農StayUp
主頁地址:https://gozhuyinglong.github.io
源碼分享:https://github.com/gozhuyinglong/blog-demos

1. 一個簡單的模擬鴨子游戲

我們先來看一個模擬鴨子的游戲:游戲中會出現各種鴨子,它們一邊游泳戲水,一邊呱呱叫。

經過一番調研后:
已知的鴨子種類有:野鴨(Mallard Duck)、紅頭鴨(Redhead Duck)、橡皮鴨(Rubber Duck)。
已知的鴨子行為有:游泳(Swim)、嘎嘎叫(Quack)、顯示鴨子的樣子(Display)。

下面是這些鴨子的外觀:

野鴨
紅頭鴨
橡皮鴨

需求明確,開搞!

1.1 是時候展示OO技術了~

為了可復用性,設計了一個鴨子超類Duck,并讓各種鴨子繼承此超類:

  • 該超類中實現了swim()quack()方法,由于每一種鴨子外觀不同,所以display()指定為抽象方法(當然該類也是抽象類)。
  • 各個子類具體實現了display()方法
  • 由于橡皮鴨不會“嗄嗄叫”,所以重寫了quack()方法為“吱吱叫”。

下面是UML類圖:


簡版UML類圖

1.2 Change!!!

我們知道軟件開發的一個不變的真理是:“變化”!

現在要求增加一種鴨子行為:飛行(Fly),該怎么做呢?

如果繼續使用繼承,那么橡皮鴨是不會飛行的,還是需要重寫fly()方法。如下:

簡版UML類圖

那如果再增加一種鴨子:誘餌鴨(Decoy Duck),這是只木頭鴨子,它即不會叫,也不會飛行......


誘餌鴨

看來繼承是不能滿足需求了!

1.3 使用接口怎么樣?

fly()方法和quack()抽離出來,做成接口,讓擁有該行為的鴨子對其進行實現。如下:

簡版UML類圖

這似乎解決了現在的問題!

但如果再增加100只鴨子呢?豈不是所有會飛行或會叫的鴨子,都要實現一遍,沒有達到代碼的復用性。而且一旦要修改某個行為將是一件痛苦的事(比如將所有“吱吱叫”改成“仿真呱呱叫”)……

1.4 封裝變化

有一個設計原則,恰好適用于上面模擬鴨子游戲的狀況。

找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。

換句話說,如果每次來新的需求,都會使某方面的代碼發生變化,那么你就可以確定,這部分的代碼需要被抽出來,和其他穩定的代碼有所區分。

這便是策略模式的精神所在,下面我們來看該模式的詳細介紹。

2. 策略模式

策略模式(Strategy Pattern)是一種行為型模式。該模式定義一系列的算法,把它們一個個封裝起來,并使它們可以相互替換。該模式讓算法的變化獨立于使用它的客戶。

Define a family of algorithms, encapsulate each one, and make them interchangeable.

該設計模式體現了幾個設計原則:

  • 封裝變化
  • 針對接口編程,而不是實現類
  • 多用組合,少用繼承

策略模式由三部分組成:

  • Strategy(策略)
    定義了所有策略的公共接口。上下文(Context)使用這個接口來調用某個具體策略(ConcreteStrategy)。
  • ConcreteStrategy(具體策略)
    Strategy接口的實現,定義了一個具體的策略實現。
  • Context(上下文)
    定義了Strategy對象如何來使用,是策略算法的調用者。
策略模式原理圖

3. 代碼實現

我們使用策略模式實現上面模擬鴨子游戲。

標準UML類圖(使用策略模式實現模擬鴨子游戲)

3.1 飛行行為實現

定義飛行行為接口

public interface Fly {
    void fly();
}

用翅膀飛行實現類

public class FlyWithWings implements Fly {
    @Override
    public void fly() {
        System.out.println("用翅膀飛行");
    }
}

不會飛行實現類

public class FlyNoWay implements Fly {
    @Override
    public void fly() {
        System.out.println("不會飛行");
    }
}

3.2 鴨叫行為實現

定義鴨叫行為接口

public interface Quack {
    void quack();
}

呱呱叫實現類

public class QuackGuaGua implements Quack {
    @Override
    public void quack() {
        System.out.println("呱呱叫");
    }
}

吱吱叫實現類

public class QuackZhiZhi implements Quack {
    @Override
    public void quack() {
        System.out.println("吱吱叫");
    }
}

不會叫實現類

public class QuackNoWay implements Quack {
    @Override
    public void quack() {
        System.out.println("不會叫");
    }
}

3.3 鴨子類的實現

定義鴨子抽象類

public abstract class Duck {

    protected Fly fly;
    protected Quack quack;

    public void swim() {
        System.out.println("正在游泳...");
    }

    public abstract void display();

    public Fly getFly() {
        return fly;
    }

    public Quack getQuack() {
        return quack;
    }
}

野鴨實現類

public class MallardDuck extends Duck {

    // 野鴨用翅膀飛行,呱呱叫
    public MallardDuck() {
        this.fly = new FlyWithWings();
        this.quack = new QuackGuaGua();
    }

    @Override
    public void display() {
        System.out.println("外觀是綠頭鴨");
    }
}

紅頭鴨實現類

public class RedheadDuck extends Duck {

    // 紅頭鴨用翅膀飛行,呱呱叫
    public RedheadDuck() {
        this.fly = new FlyWithWings();
        this.quack = new QuackGuaGua();
    }

    @Override
    public void display() {
        System.out.println("外觀是紅頭鴨");
    }
}

橡皮鴨實現類

public class RubberDuck extends Duck {

    // 橡皮鴨不會飛行,吱吱叫
    public RubberDuck() {
        this.fly = new FlyNoWay();
        this.quack = new QuackZhiZhi();
    }

    @Override
    public void display() {
        System.out.println("外觀是橡皮鴨");
    }
}

誘餌鴨實現類

public class DecoyDuck extends Duck {

    // 誘餌鴨不會飛行,也不會叫
    public DecoyDuck() {
        this.fly = new FlyNoWay();
        this.quack = new QuackNoWay();
    }

    @Override
    public void display() {
        System.out.println("外觀是誘餌鴨");
    }
}

3.4 測試

編寫簡單測試類

public class Test {

    public static void main(String[] args) {
        MallardDuck mallardDuck = new MallardDuck();
        mallardDuck.display();
        mallardDuck.swim();
        mallardDuck.getFly().fly();
        mallardDuck.getQuack().quack();

        System.out.println("-------------------");

        DecoyDuck decoyDuck = new DecoyDuck();
        decoyDuck.display();
        decoyDuck.swim();
        decoyDuck.getFly().fly();
        decoyDuck.getQuack().quack();
    }
}

輸出結果

外觀是綠頭鴨
正在游泳...
用翅膀飛行
呱呱叫
-------------------
外觀是誘餌鴨
正在游泳...
不會飛行
不會叫

4. 完整代碼

完整代碼請訪問我的Github,若對你有幫助,歡迎給個Star,謝謝!

https://github.com/gozhuyinglong/blog-demos/tree/main/design-patterns/src/main/java/io/github/gozhuyinglong/designpatterns/strategy

5. 參考

推薦閱讀

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容