什麼是策略模式?
策略模式是指有一定行動內容的相對穩定的策略名稱。策略模式在古代中又稱“計策”,簡稱“計”,如《漢書·高帝紀上》:“漢王從其計”。這裏的“計”指的就是計謀、策略。策略模式具有相對穩定的形式,如“避實就虛”、“出奇制勝”等。一定的策略模式,既可應用於戰略決策,也可應用於戰術決策;既可實施於大系統的全局性行動,也可實施於大系統的局部性行動(百度百科)。
策略上模式:定義一族算法類,將每個算法分別封裝起來,讓它們可以互相替換。策略模式可以使算法的變化獨立於使用它們的客戶端(這裏的客戶端代指使用算法的代碼)。
其實策略模式也是爲了實現解耦的,不過它和工廠不同的是它是對策略的定義、創建、使用這三部分進行解耦,下面我們一起來看看如何使用這美妙的策略模式把。
講了這麼多到底什麼是策略模式呢?概念理解起來比較抽象,這個時候就需要引入一個生活中的栗子,小明很喜歡旅遊,只要一有時間就會到世界各地遊玩,但是有一個問題一直在困擾着他,那就是出行工具問題,是選擇飛機呢?高鐵呢?還是火車,每次都會考慮很久,直到有一天,它得到了一個狗頭軍師,每次出行都是這個狗頭軍師爲它安排出行的方式,小明只需要把目的地告訴狗頭軍師,狗頭軍師爲它選擇最合適的出行工具,這就是現實生活中的策略,狗頭軍師就相當於程序中的策略接口,飛機、高鐵、火車則是真正的策略類(用於解決問題)。
策略的實現
設計原則中一直推薦我們使用面向接口編程而非實現,策略模式也是如此,它需要先定義一個策略接口,然後在定義一組策略類來實現策略接口,這個時候我們只需要關注的是接口,而並非是那一組的策略類。
/**
/**
* 策略接口
*/
public interface IStrategy {
String strategyTravel();
}
/**
* 飛機
*/
@Service
public class AircraftStrategyImpl implements IStrategy{
@Override
public String strategyTravel() {
return null;
}
}
/**
* 高鐵
*/
@Service
public class SHRStrategyImpl implements IStrategy{
@Override
public String strategyTravel() {
return null;
}
}
/**
* 火車
*/
@Service
public class TrainStrategyImpl implements IStrategy {
@Override
public String strategyTravel() {
return null;
}
}
上面的代碼就是一個簡單的策略模式,我們定義了一個策略接口:IStrategy,同時定義了一組策略類:AircraftStrategyImpl、SHRStrategyImpl、TrainStrategyImpl,對於小明來說,它需要對接的是策略接口,告訴策略接口自己要去的地方離自己有多遠,策略模式經過自己的謀略就會計算出最佳的出行工具了。
哈哈,策略模式的實現是不是特別簡單,細心的朋友一眼就發現了,這不就是多態的特性嗎?怎麼就成策略模式了呢?策略是模式 多態是技術 策略側重應用場景 多態側重代碼實現,所以大家不要把多態和策略模式想成是一個東西,雖然策略是運用了多態的技術。
策略模式的結構我們是熟悉了,但是策略接口和策略類怎麼創建呢?或者說怎麼綁定他們之間的關係?大家可以先思考一下,然後接着往下看。
如何創建策略?
我們的策略類有很多個,一般情況下我們需要通過一個type來區分使用哪個策略,小明出行的這個例子,也是通過類型來區分,比如:國外遊選擇飛機;國內省外選擇高鐵;國內省內選擇火車,那具體怎麼創建呢?不知道大家對工廠模式還熟悉嗎?
沒錯,我們就可以通過工廠來創建策略類,我們叫他策略工廠,熟悉工廠模式的同學應該知道工廠模式有兩個經典的實現方式:餓漢式、懶漢式,我們的策略工廠也可以根據具體的需求來創建策略,我們先來看看餓漢式。
/**
* 策略工廠
*/
@Component
public class StrategyFactory {
/**
* 用於存儲策略類,爲什麼使用HashMap而不用ConcurrentHashMap呢?
* 那是因爲它沒有併發問題,所以不需要使用ConcurrentHashMap
*/
private static final Map<Integer,IStrategy> strategy = new HashMap<Integer, IStrategy>();
static{
//國外 選擇飛機
strategy.put(1,new AircraftStrategyImpl());
//國內--省外 選擇高鐵
strategy.put(1,new SHRStrategyImpl());
//國內--省內 選擇火車
strategy.put(1,new TrainStrategyImpl());
}
public static IStrategy getStrategy(Integer type){
if(type == null ){
throw new IllegalArgumentException("請選擇類型");
}
return strategy.get(type);
}
}
餓漢式就是在程序啓動的時候將所有的策略類創建完成,需要使用的時候直接調用即可,那什麼時候使用這種方式呢?對象的創建很複雜的時候,比如策略類的創建需要通過計算、引用各種類,創建策略類的時候會花費大量的時間,按照 fail-fast 的設計原則(有問題及早暴露),推薦使用餓漢式;如果策略類中沒有共享變量,無狀態,僅僅只是用於謀略(計算),這個時候我們也可以使用餓漢式。
那懶漢式呢?我們先來看看懶漢式的實現
/**
* 策略工廠
*/
@Component
public class StrategyFactory {
public static IStrategy getStrategy(Integer type){
if(type == null ){
throw new IllegalArgumentException("請選擇類型");
}
if(type == 1){
//國外 選擇飛機
return new AircraftStrategyImpl();
}else if(type == 2){
//國內--省外 選擇高鐵
return new SHRStrategyImpl();
}else if(type == 3){
//國內--省內 選擇火車
return new TrainStrategyImpl();
}
return null;
}
}
懶漢式則是在調用的時候創建,根據調用的類型實時創建對應的策略,這種模式是用於存在共享變量的策略類,因爲存在共享就會存在併發安全問題,所以懶漢式是一個不錯的選擇,至於選擇哪種,相信大家已經有了自己的答案了。
策略模式的使用
策略模式的結構、創建都已經講清楚了,那我們如何使用策略模式呢?就算我們之前寫的代碼再漂亮,如果不能使用,那將毫無意義,接下來我們一起來看看如何使用策略模式吧。
話不多說,直接上代碼,新建Context策略行爲類:
/**
* Context 改變策略 Strategy 行爲變化。
*/
public class Context {
private IStrategy strategy;
public Context(IStrategy strategy){
this.strategy = strategy;
}
public String strategyTravel(){
return this.strategy.strategyTravel();
}
}
對之前的策略類做了小小的改動
/**
* 飛機
*/
@Service
public class AircraftStrategyImpl implements IStrategy{
@Override
public String strategyTravel() {
return "國外,當然是選擇飛機出行了!";
}
}
/**
* 高鐵
*/
@Service
public class SHRStrategyImpl implements IStrategy{
@Override
public String strategyTravel() {
return "省外,當然是高鐵啊,速度槓槓的!";
}
}
/**
* 火車
*/
@Service
public class TrainStrategyImpl implements IStrategy {
@Override
public String strategyTravel() {
return "既然是省內,那就是選擇火車出行吧!";
}
}
測試代碼如下
public class StrategyTest {
public static void main(String[] args) {
//國外
IStrategy strategy1 = StrategyFactory.getStrategy(1);
Context context1 = new Context(strategy1);
String tool1 = context1.strategyTravel();
System.out.println( tool1);
//國內--省外
IStrategy strategy2 = StrategyFactory.getStrategy(2);
Context context2 = new Context(strategy2);
String tool2 = context2.strategyTravel();
System.out.println(tool2);
//國內--省內
IStrategy strategy3 = StrategyFactory.getStrategy(3);
Context context3 = new Context(strategy3);
String tool3 = context3.strategyTravel();
System.out.println(tool3);
}
}
這樣,完整的策略模式代碼就完成了,我們看看運行結果:
這次的出行工具爲:國外,當然是選擇飛機出行了!
這次的出行工具爲:省外,當然是高鐵啊,速度槓槓的!
這次的出行工具爲:既然是省內,那就是選擇火車出行吧!
Process finished with exit code 0
完美,大家知道爲什麼需要引入一個Context嗎?文章的最開始講到這麼一句話,不知道大家還記得嗎?
策略上模式:定義一組算法類,將每個算法分別封裝起來,讓它們可以互相替換。
這個Context就是爲這句話而生的,好了,策略模式的基本使用已經講的差不多了。
爲什麼要使用策略模式?
策略模式雖然講完了,但是大家都知道了什麼時候該使用策略模式嗎?我來舉幾個例子:
1.會員等級的折扣,很多時候會員等級的不同買東西享受的折扣也是不一樣的,比如:一級的時候只能享受九五折;二級可以享受八五折,三級之後享受七折同時支持買三送一,這個時候推薦使用策略模式,有意想不到的驚喜。
2.文件的讀取,我們知道文件類型有很多,像java開發的人應該對yml和properties很熟悉,他們的數據格式是完全不相同的,所以對它們的操作也可以使用策略模式。
3.日誌的處理,爲什麼日誌的處理也可以使用策略模式呢?日誌中肯定會出現很多的報錯信息,比如:數據庫的錯誤信息、前端傳遞的參數錯誤信息、業務的邏輯錯誤信息等等,我們需要對這些錯誤信息進行不同的補救方式,所以策略是一個好選擇。
其實說到底策略模式適用於許多相關類但是行爲不同,都可以使用策略模式。
總結
設計模式不能濫用,不能爲了使用設計模式而使用設計模式,我們要在合適的地方使用合適的方式,並不一定使用設計模式就是最好的,所以一定要結合實際場景。