從泰倫盧帶詹姆斯奪冠看Java設計模式系列之——策略模式

前言

前面講完了設計模式的創建型模式和結構型模式,不知道一路下來有沒有過陪伴的讀者,因爲我是三天打魚兩天曬網的模式,所以可能很久纔會更新一篇,但是就這樣以這種隨性的方式堅持着更新,我自己覺得挺好,本身能力水平有限,文章質量可能也是層次不齊,所以還是希望大家多擔待些,給我們一起成長的時間和空間。

接下來準備講剩下的行爲型模式了,因爲設計模式本身可能看起來比較枯燥,每次幾千字碼下來,估計有耐心看的人不多,所以我想了一下,剩下的設計模式我儘量找一些生活中常見的例子,結合着實際場景講,希望能幫助大家更容易理解,如果講得不好,還是請見諒!

 

不知道有沒有碼之初的小夥伴看NBA,看過NBA的一定會認識上圖的傳奇人物,沒錯,他就是我們今天的男豬腳——泰倫盧。人稱戰術大師、戰術鬼才盧教頭。關於他的傳奇,坊間有太多的傳說,感興趣的小夥伴可以去了解一下,保證你不會失望,你會愛他恨他甚至有時候還想打他,但不管怎樣,他永遠那麼可愛,因爲他就是——

 

今天,我就帶大家來揭祕,深入瞭解一下,爲何大家都叫他戰術鬼才,爲何騎士勇士球迷都深愛着他,盧教頭到底有何魅力,在短短四年間吸粉無數,成爲新一代NBA的流量小生,對不起,流量教練。

 

我們先來回顧一下騎士勇士球迷都無法忘懷的2015-16賽季的總決賽,那一年騎士以總比分4:3力克勇士,成爲史上第一支總決賽1:3落後翻盤的冠軍球隊,也讓勇士成爲史上第一支常規賽73勝卻無緣總冠軍的球隊,球迷相愛相殺互撕的部分這裏就不談了,我們談談爲何騎士那一年拿到了總冠軍。是因爲詹姆斯的統治力嗎?還是因爲歐文樂福的王者輔助?又或是因爲騎士全員的衆志成城?錯,你們都錯了,是因爲他——戰術鬼才盧指導。

 

可能沒看過球的小夥伴還是一臉懵逼,說到現在說了個什麼J&B,別慌,這就爲大家講爲什麼贏球的功勞是盧指導的,盧指導的戰術究竟如何神奇?請看盧指導的戰術騷操作:

又或者

 

你是否領略到了戰術鬼才的魅力了呢?如果你沒有意識到盧指導有多麼厲害,那麼你可能真的不懂球。戰術大師的備戰計劃,比賽策略都獨樹一幟,無論媒體什麼時候採訪戰術問題時,盧指導都能胸有成竹的說:

 

看到這兒,你是否有疑慮,這真的就是我前面吹噓的傳奇教練嗎?我是不是在黑他?拜託,我真的沒有黑他,我還沒有說完呢,既然叫戰術鬼才,怎麼可能就一套戰術,就一套比賽策略呢?是不是?那麼,盧指導的策略到底是什麼呢?我們來一探究竟。

什麼是策略模式

策略模式(Strategy Pattern):定義了算法家族,分別封裝起來,讓他們之間可以互相替換,此模式讓算法的變化獨立於使用算法的客戶,也就是算法的變化不會影響到使用算法的客戶。

定義一組算法,將每一個算法封裝起來,使這些算法可以相互切換。既然是一組算法,說明是不同的策略。可以相互切換,說明這些策略實現了相同的接口或者繼承了相同的抽象類。

通俗點理解就是,解決一個問題,有多種辦法策略。就像你追女孩子,通常的策略就是請喫飯、看電影、送花、噓寒問暖等,這些就是不同的策略,都實現追女孩子這個方法,你想先送花還是先請喫飯,都由你自己決定,也可以隨意切換。

那我們可愛的盧指導呢?我們都知道NBA一般是四節比賽,除非進加時賽,每場都要面對不同的對手,每一節比賽場上形勢都瞬息萬變,這就需要我們的盧指導日夜操勞,隨時制定不同的比賽計劃和策略去應對,這是一樣的道理。

策略模式的三個角色

  • Strategy(抽象策略角色):通常由一個接口或抽象類實現。此角色定義了所有的具體策略類的公共接口。
  • ConcreteStrategy(具體策略角色): 實現了抽象策略類,包裝了相關的算法或行爲。
  • Context(上下文角色): 上層訪問策略的入口,它持有抽象策略角色Strategy的引用。

假如一共有四節比賽,每節比賽都需要一個比賽策略,也就是戰術,那我們就可以抽象出一個比賽策略的接口,裏面有個制定戰術的方法。每一節的比賽策略就是具體策略角色,都實現了制定戰術的方法。那麼有同學不禁要問了,盧指導在策略模式裏充當什麼角色呢?沒錯,盧指導就是Context上下文角色,球員上場都需要先問盧指導打什麼戰術,執行哪一個計劃策略,盧指導就給他們相應的比賽策略。下面我們用代碼來實現一下策略模式,哦,抱歉,用代碼來看一下盧指導到底是個什麼樣的戰術鬼才。

盧指導的戰術演示(策略模式代碼實例)

1、編寫抽象策略接口

package com.weiya.mazhichu.designpatterns.strategy;

/**
 * <p class="detail">
 * 功能: Strategy抽象策略角色類:比賽策略接口,也可以是抽象類
 * </p>
 *
 * @author Moore
 * @ClassName Game plan.
 * @Version V1.0.
 * @date 2019.09.09 10:26:05
 */
public interface GameStrategy {

    /**
     * 制定比賽計劃
     */
    void plan();
}

 

2、編寫具體策略類,實現抽象策略接口

package com.weiya.mazhichu.designpatterns.strategy;

/**
 * <p class="detail">
 * 功能: 具體策略角色
 * </p>
 *
 * @author Moore
 * @ClassName First game strategy.
 * @Version V1.0.
 * @date 2019.09.09 17:58:14
 */
public class FirstGameStrategy implements GameStrategy {

    /**
     * 制定比賽計劃
     */
    @Override
    public void plan() {
        System.err.println("比賽計劃是:把球給樂福單打,如果拉不開分差就把球給詹姆斯,詹姆斯快想想辦法!");
    }
}

 

package com.weiya.mazhichu.designpatterns.strategy;

/**
 * <p class="detail">
 * 功能: 具體策略角色
 * </p>
 *
 * @author Moore
 * @ClassName First game strategy.
 * @Version V1.0.
 * @date 2019.09.09 17:58:14
 */
public class SecondGameStrategy implements GameStrategy {

    /**
     * 制定比賽計劃
     */
    @Override
    public void plan() {
        System.err.println("比賽計劃是:把球給歐文單打,如果比分落後就把球給詹姆斯,詹姆斯快想想辦法!");
    }
}

 

package com.weiya.mazhichu.designpatterns.strategy;

/**
 * <p class="detail">
 * 功能: 具體策略角色
 * </p>
 *
 * @author Moore
 * @ClassName First game strategy.
 * @Version V1.0.
 * @date 2019.09.09 17:58:14
 */
public class ThirdGameStrategy implements GameStrategy {

    /**
     * 制定比賽計劃
     */
    @Override
    public void plan() {
        System.err.println("比賽計劃是:把球給JR和姐夫格林投三分,如果都投不進把球給詹姆斯,詹姆斯快想想辦法!");
    }
}

 

package com.weiya.mazhichu.designpatterns.strategy;

/**
 * <p class="detail">
 * 功能: 具體策略角色
 * </p>
 *
 * @author Moore
 * @ClassName First game strategy.
 * @Version V1.0.
 * @date 2019.09.09 17:58:14
 */
public class LastTimeGameStrategy implements GameStrategy {

    /**
     * 制定比賽計劃
     */
    @Override
    public void plan() {
        System.err.println("比賽計劃是:把球給詹姆斯,詹姆斯快想想辦法!");
    }
}

3、戰術大師盧指導登場,編寫Context上下文類:

package com.weiya.mazhichu.designpatterns.strategy;

import lombok.Getter;
import lombok.Setter;

/**
 * <p class="detail">
 * 功能:Context角色,持有Strategy角色的引用
 * </p>
 *
 * @author Moore
 * @ClassName Tyronn lue.
 * @Version V1.0.
 * @date 2019.09.10 10:09:56
 */
public class TyronnLue {
    @Setter
    private GameStrategy strategy;

    public TyronnLue(GameStrategy strategy) {
        this.strategy = strategy;
    }

    /**
     * <p class="detail">
     * 功能:
     * </p>
     *
     * @author Moore
     * @date 2019.09.10 10:09:56
     */
    public void play(){
        strategy.plan();
    }
}

4、客戶端調用(帶大家回顧一下2015-16賽季NBA總決賽第七場):

package com.weiya.mazhichu.designpatterns.strategy;

/**
 * <p class="detail">
 * 功能:總決賽,客戶端引用
 * </p>
 *
 * @author Moore
 * @ClassName Finals.
 * @Version V1.0.
 * @date 2019.09.10 10:12:17
 */
public class Finals {
    /**
     * 主程序入口.
     *
     * @param args :
     * @author Moore
     * @date 2019.09.10 10:12:17
     */
    public static void main(String[] args) {

        System.out.println("---------2015-2016賽季總決賽騎士VS勇士比賽開始!---------"+"\n\n");
        System.out.println("------戰術鬼才盧指導制定了4節比賽計劃,並一一告訴了球員----------");
        System.out.println("---------第一節比賽開始,騎士球員拿出第一節的比賽計劃---------");
        GameStrategy first = new FirstGameStrategy();
        TyronnLue strategy = new TyronnLue(first);
        strategy.play();
        System.out.println("---------第一節比賽結束,騎士23:22領先-------"+"\n\n");

        System.out.println("---------第二節開始,騎士球員拿出第二節比賽計劃---------");
        GameStrategy second = new SecondGameStrategy();
        strategy.setStrategy(second);
        strategy.play();
        System.out.println("---------半場結束,勇士49:42領先騎士7分----------"+"\n\n");

        System.out.println("---------第三節開始,騎士球員拿出第三節比賽計劃---------");
        GameStrategy third = new ThirdGameStrategy();
        strategy.setStrategy(third);
        strategy.play();
        System.out.println("---------第三節結束,騎士將比分追至75:76----------"+"\n\n");


        System.out.println("---------第四節比賽雙方比分交替領先,不知不覺來到了最後三分鐘,兩隊戰平---------");
        System.out.println("---------騎士請求暫停,球員拿出關鍵時刻比賽計劃---------");

        GameStrategy last = new LastTimeGameStrategy();
        strategy.setStrategy(last);
        strategy.play();

        System.out.println("---------歐文在比賽時間還剩53秒時命中關鍵三分,幫助騎士領先3分---------");
        System.out.println("---------庫裏3分不中,歐文上籃不中,詹姆斯追冒伊戈達拉,詹姆斯兩罰一中,鎖定勝局!---------");
        System.out.println("---------最終騎士隊93-89力克勇士,以總比分4-3險勝奪冠,他們成爲史上第一支在總決賽實現1-3落後翻盤的球隊,獲得2015-16賽季總冠軍----------");
        System.out.println("---------詹姆斯抱着樂福哭了,戰術大師泰倫盧也哭了!---------");

    }
}

 

運行結果,看一下整場比賽,盧指導是怎樣制定策略,帶領詹歐樂體驗冠軍經歷的:

 

至此,大家應該知道了盧指導的戰術鬼才,也知道了什麼是策略模式,那麼策略模式有什麼優缺點呢?

策略模式優點

  • 算法可以自由切換(每節比賽的比賽計劃都可以隨意切換)。
  • 避免使用多重條件判斷。
  • 擴展性好(如果要加新的算法,只要實現抽象策略類就好了,假如進了加時賽,盧指導就要制定新的比賽策略就好了)。

策略模式缺點

  • 客戶端必須知道所有的類,並且自己決定使用哪個類,也就是所有策略類都要暴露給客戶端,對客戶端也是個負擔(球員必須一開始就知道所有的比賽策略,但是籃球場形式瞬息萬變,戰術太多,球員要一下子記得所有的戰術太不容易)。
  • 策略類會很多。

策略模式+簡單工廠

你以爲故事就結束了嗎?我們可愛的盧指導既然已經知道這種比賽策略有缺點,而且已經有媒體和騎士內部球員開始說閒話,說他根本就沒有什麼戰術,就知道明牌託管,所有的戰術最後都是詹姆斯快想想辦法,一個總冠軍還是抱詹姆斯大腿得的,隨便換哪個教練都行,這下可傷到了盧指導的自尊了,於是他決定再也不明牌給球員和媒體了,他要像波波維奇那樣,成爲真正的受人尊敬愛戴的戰術大師,那該怎麼辦呢?

 

 

盧指導回家想了一個休賽期,決定不再明牌,也不一開始就告訴球員所有的戰術,不讓球員自主選擇戰術了,要讓球員主動來問自己戰術,要樹立自己的教練威望。於是盧教練第二天就執行了該想法,我們來看看盧指導怎麼改的:

修改Context上下文類,由盧指導根據球員請求,盧指導動態決定給他們哪一項比賽策略,球員只需要上場打比賽執行給到的比賽計劃就行了,不用一開始就知道整個賽季所有的具體比賽計劃(具體策略類),代碼如下:

1、編寫Context上下文,利用簡單工廠模式:

package com.weiya.mazhichu.designpatterns.strategy;

import lombok.Setter;

/**
 * <p class="detail">
 * 功能:Context角色,持有Strategy角色的引用
 * </p>
 *
 * @author Moore
 * @ClassName Tyronn lue.
 * @Version V1.0.
 * @date 2019.09.10 10:09:56
 */
public class TyronnLueContext {

    private GameStrategy strategy;

    /**
     * <p class="detail">
     * 功能:
     * </p>
     *
     * @author Moore
     * @date 2019.09.10 10:09:56
     */
    public void play(String time){
        switch (time) {
            case "first":
                strategy = new FirstGameStrategy();
                break;
            case "second":
                strategy = new ThirdGameStrategy();
                break;
            case "third":
                System.err.println("歐文去了凱爾特人了,還是執行上一節的比賽策略吧");
                strategy = new ThirdGameStrategy();
                break;
            case "last":
                strategy = new LastTimeGameStrategy();
                break;
        }
        strategy.plan();
    }
}

 

就這樣一個賽季過去了,盧教練都靠這種方式執行比賽計劃,但是質疑聲還是不絕於耳,盧教練心想:盧瑟,等我再帶這幫崽子拿到總冠軍,你們就知道我的厲害了,到時候都得乖乖的叫我諸葛-波波-泰倫盧了,嘿嘿。盧指導想到這,嘴角不自覺的上揚起來。轉眼間,2017-2018賽季總決賽來了,詹姆斯帶領騎士艱難的再次站上總決賽的舞臺,面對老對手勇士。下面看看兩隊的比賽過程,以及盧指導的神策略。

2、客戶端調用

package com.weiya.mazhichu.designpatterns.strategy;

/**
 * <p class="detail">
 * 功能: 2018總決賽,客戶端引用
 * </p>
 *
 * @author Moore
 * @ClassName Finals.
 * @Version V1.0.
 * @date 2019.09.10 10:12:17
 */
public class Finals2018 {
    /**
     * 主程序入口.
     *
     * @param args :
     * @author Moore
     * @date 2019.09.10 10:12:17
     */
    public static void main(String[] args) {

        System.out.println("---------2017-2018賽季總決賽騎士VS勇士第一場比賽正式開始!---------"+"\n\n");

        System.out.println("------戰術鬼才盧指導制定了比賽計劃,讓球員來詢問自己的時候,再告訴他們怎麼執行----------");

        System.out.println("---------第一節比賽開始,騎士球員請求第一節的比賽計劃---------");
        TyronnLueContext strategy = new TyronnLueContext();
        strategy.play("first");
        System.out.println("---------第一節比賽結束-------"+"\n\n");

        System.out.println("---------第二節開始,騎士球員拿出第二節比賽計劃---------");
        TyronnLueContext strategy2 = new TyronnLueContext();
        strategy2.play("second");
        System.out.println("---------半場結束----------"+"\n\n");

        System.out.println("---------第三節開始,騎士球員拿出第三節比賽計劃---------");
        TyronnLueContext strategy3 = new TyronnLueContext();
        strategy3.play("third");
        System.out.println("---------第三節結束----------"+"\n\n");


        System.out.println("---------第四節比賽騎士一直領先,最後時刻勇士追平比分---------");


        System.out.println("--------最後9秒,JR搶到籃板,詹姆斯想問問盧指導有沒有暫停,想請求比賽計劃----------");

        TyronnLueContext strategy4 = new TyronnLueContext();

        System.err.println("原本詹姆斯想讓JR請求的關鍵時刻的戰術是--->");
        strategy4.play("last");

        System.out.println("---------但是盧指導懵逼中,不知道有沒有暫停,也忘記返回比賽策略---------");
        System.out.println("---------JR也懵逼了,抱着球思考人生,時間走完,勇士將比賽拖進加時賽---------");
        System.out.println("---------詹姆斯心灰意冷砍下50分,勇士士氣正旺,4:0橫掃騎士獲得2017-2018賽季總冠軍!----------");

    }
}

 

運行,查看結果:

 

至此,我們又一次見到了盧教練的神操作,雖然改善了策略模式,但是因爲自己和球員有時候神經短路,最終騎士輸掉了總冠軍。詹姆斯遠走洛杉磯,盧指導也解甲歸田,江湖從此只剩下他的傳說了。

我們先不關心改變策略帶來的結果,我們就只看一下策略模式結合簡單工廠模式帶來的優缺點。

優點

  • 策略模式與簡單工廠結合的用法,客戶端就只需要認識一個Context上下文工廠類就可以了。耦合更加降低,達到解耦目的。
  • 抽象策略的方法和客戶端分離,客戶端不用關心具體策略怎麼制定算法的,只要通過上下文對象就可以拿到算法結果。

缺點

  • 每次增加算法,除了需要實現抽象策略接口,還要修改Context上下文工廠類,違背了“開閉原則”。
  • Context上下文類中if/else或則swith/case判斷條件太多,不易維護。

策略模式+簡單工廠+反射

最近聽說盧指導要復出了,目的地是洛杉磯快船隊,和雞湯教父裏弗斯相聚,雙份雞湯戰術大師相聚,加上卡哇伊喬治和一衆狠角色,新賽季勢必勢如破竹,隔壁湖人隊更衣室某老漢凶多吉少。

這兒要說的是,聽說盧指導休息的這一年可沒有白閒着,在家閉門修煉策略模式,他覺得18賽季的比賽策略不是很好,雖然球員和媒體不知道他變幻無常的戰術了,但是他自己作爲上下文類,裏面有太多的if/else和swith/case條件判斷,一個賽季82場比賽,要寫那麼多條件,太爲難本盧寶寶了。

於是,盧寶寶又想了個好辦法,他決定自己不去管理這麼多具體策略了,每次只要有新增的策略,就交給另一個助教去保管,這樣每次球員請求比賽策略的時候,他只要從助教那兒拿過來就好了。想到這,盧寶寶又開心的笑起來了,心想自己真是個戰術天才,嘿嘿。

那我們就來看看盧指導是怎樣改變他的策略的。

1、由助教管理所有的具體策略,這兒用枚舉類。

package com.weiya.mazhichu.designpatterns.strategy;

import lombok.Getter;
import lombok.Setter;

/**
 * <p class="detail">
 * 功能: 所有具體策略類放枚舉裏
 * </p>
 *
 * @author Moore
 * @ClassName Concrete strategy enum.
 * @Version V1.0.
 * @date 2019.09.11 09:59:40
 */
public enum ConcreteStrategyEnum {
    FIRST("com.weiya.mazhichu.designpattern.strategy.FirstGameStrategy"),
    SECOND("com.weiya.mazhichu.designpattern.strategy.SecondGameStrategy"),
    THIRD("com.weiya.mazhichu.designpattern.strategy.ThirdGameStrategy"),
    LAST("com.weiya.mazhichu.designpattern.strategy.LastGameStrategy"),
    ;

    @Getter
    @Setter
    private String className;

    ConcreteStrategyEnum(String className) {
        this.className = className;
    }
}

 

2、盧寶寶自己不用那麼多條件判斷了,改用反射獲取,修改Context上下文類

package com.weiya.mazhichu.designpatterns.strategy;

/**
 * <p class="detail">
 * 功能:Context角色,持有Strategy角色的引用
 * </p>
 *
 * @author Moore
 * @ClassName Tyronn lue.
 * @Version V1.0.
 * @date 2019.09.10 10:09:56
 */
public class TyronnLueReflectContext {

    private GameStrategy strategy;

    /**
     * <p class="detail">
     * 功能: 利用反射動態獲取具體策略類
     * </p>
     *
     * @param time :
     * @throws Exception the exception
     * @author Moore
     * @date 2019.09.11 10:02:20
     */

    public static GameStrategy getStrategy(String time) throws Exception {
        String className = ConcreteStrategyEnum.valueOf(time).getClassName();
        return (GameStrategy) Class.forName(className).newInstance();
    }
}

 

3、新賽季還未開始,又因盧教練對16賽季的騎士還有感情,所以就用老隊員模擬了球員請求比賽策略的場景

package com.weiya.mazhichu.designpatterns.strategy;

/**
 * <p class="detail">
 * 功能:盧指導模擬客戶端請求結合工廠模式+反射的策略模式
 * </p>
 *
 * @author Moore
 * @ClassName Nba 2019.
 * @Version V1.0.
 * @date 2019.09.11 10:15:29
 */
public class NBA2019 {

    public static void main(String[] args) throws Exception {

        System.out.println("---------2019-20賽季揭幕戰---------"+"\n\n");

        System.out.println("------戰術鬼才盧指導制定了比賽計劃,讓球員來詢問自己的時候,再告訴他們怎麼執行----------"+"\n\n");

        System.out.println("---------第一節比賽開始,騎士球員請求第一節的比賽計劃---------");
        GameStrategy strategy = TyronnLueReflectContext.getStrategy("FIRST");
        strategy.plan();
        System.out.println("---------第一節比賽結束-------"+"\n\n");

        System.out.println("---------第二節開始,騎士球員請求第二節比賽計劃---------");
        strategy = TyronnLueReflectContext.getStrategy("SECOND");
        strategy.plan();
        System.out.println("---------半場結束----------"+"\n\n");

        System.out.println("---------第三節開始,騎士球員請求第三節比賽計劃---------");
        strategy = TyronnLueReflectContext.getStrategy("THIRD");
        strategy.plan();
        System.out.println("---------第三節結束----------"+"\n\n");


        System.out.println("---------第四節雙方交替領先,比賽來到最後兩分鐘,騎士球員請求關鍵時刻戰術---------");
        strategy = TyronnLueReflectContext.getStrategy("LAST");
        strategy.plan();
        System.out.println("---------比賽結束,詹姆斯35+9+8準三雙率隊險勝快船,取得開門紅!----------");

    }
}

 

運行,看看盧指導的模擬結果:

 

我不知道盧寶寶新賽季會展現什麼樣的神操作,但至少他帶我們一步步深入理解了策略模式,我們看一下結合工廠模式+反射的策略模式,做了哪些改進,有什麼好處。

優點

  • 去除了Context中的條件判斷,使代碼易於讀懂和維護。
  • 擴展性好,增加算法只需要增加對應的策略實現類和枚舉,符合開閉原則。

好了,策略模式就講到這兒了,希望在盧指導的buff加成下,大家都能理解策略模式。最後,不知道說什麼了,再來一個盧指導的表情包吧,也送給所有碼之初的小夥伴。

 

我不能保證我寫的文章都是正確的,但是我能保證都是我自己花時間用心寫的,所有的代碼示例都是原創,所有的理解都只是我個人理解,不能代表官方權威,所以請各位讀者閱讀時帶着批判的眼光,有選擇性的認同,謝謝!

更多的設計模式文章在我的公衆號“碼之初”同步更新,如有興趣,可以關注閱讀,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章