策略模式(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. 參考

推薦閱讀

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容