前言
前面講完了設計模式的創建型模式和結構型模式,不知道一路下來有沒有過陪伴的讀者,因爲我是三天打魚兩天曬網的模式,所以可能很久纔會更新一篇,但是就這樣以這種隨性的方式堅持着更新,我自己覺得挺好,本身能力水平有限,文章質量可能也是層次不齊,所以還是希望大家多擔待些,給我們一起成長的時間和空間。
接下來準備講剩下的行爲型模式了,因爲設計模式本身可能看起來比較枯燥,每次幾千字碼下來,估計有耐心看的人不多,所以我想了一下,剩下的設計模式我儘量找一些生活中常見的例子,結合着實際場景講,希望能幫助大家更容易理解,如果講得不好,還是請見諒!
不知道有沒有碼之初的小夥伴看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加成下,大家都能理解策略模式。最後,不知道說什麼了,再來一個盧指導的表情包吧,也送給所有碼之初的小夥伴。
我不能保證我寫的文章都是正確的,但是我能保證都是我自己花時間用心寫的,所有的代碼示例都是原創,所有的理解都只是我個人理解,不能代表官方權威,所以請各位讀者閱讀時帶着批判的眼光,有選擇性的認同,謝謝!
更多的設計模式文章在我的公衆號“碼之初”同步更新,如有興趣,可以關注閱讀,謝謝!