【設計模式】策略模式:我是一個有謀略的類

什麼是策略模式?

策略模式是指有一定行動內容的相對穩定的策略名稱。策略模式在古代中又稱“計策”,簡稱“計”,如《漢書·高帝紀上》:“漢王從其計”。這裏的“計”指的就是計謀、策略。策略模式具有相對穩定的形式,如“避實就虛”、“出奇制勝”等。一定的策略模式,既可應用於戰略決策,也可應用於戰術決策;既可實施於大系統的全局性行動,也可實施於大系統的局部性行動(百度百科)。

策略上模式:定義一族算法類,將每個算法分別封裝起來,讓它們可以互相替換。策略模式可以使算法的變化獨立於使用它們的客戶端(這裏的客戶端代指使用算法的代碼)。

其實策略模式也是爲了實現解耦的,不過它和工廠不同的是它是對策略的定義、創建、使用這三部分進行解耦,下面我們一起來看看如何使用這美妙的策略模式把。

講了這麼多到底什麼是策略模式呢?概念理解起來比較抽象,這個時候就需要引入一個生活中的栗子,小明很喜歡旅遊,只要一有時間就會到世界各地遊玩,但是有一個問題一直在困擾着他,那就是出行工具問題,是選擇飛機呢?高鐵呢?還是火車,每次都會考慮很久,直到有一天,它得到了一個狗頭軍師,每次出行都是這個狗頭軍師爲它安排出行的方式,小明只需要把目的地告訴狗頭軍師,狗頭軍師爲它選擇最合適的出行工具,這就是現實生活中的策略,狗頭軍師就相當於程序中的策略接口,飛機、高鐵、火車則是真正的策略類(用於解決問題)。

策略的實現

設計原則中一直推薦我們使用面向接口編程而非實現,策略模式也是如此,它需要先定義一個策略接口,然後在定義一組策略類來實現策略接口,這個時候我們只需要關注的是接口,而並非是那一組的策略類。

/**
/**
 * 策略接口
 */
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.日誌的處理,爲什麼日誌的處理也可以使用策略模式呢?​日誌中肯定會出現很多的報錯信息,比如:​數據庫的錯誤信息、前端傳遞的參數錯誤信息、業務的邏輯錯誤信息等等,我們需要對這些錯誤信息進行不同的補救方式,所以策略是一個好選擇。

其實說到底策略模式適用於許多相關類但是行爲不同,都可以使用策略模式。

總結

設計模式不能濫用,不能爲了使用設計模式而使用設計模式,我們要在合適的地方使用合適的方式,並不一定使用設計模式就是最好的,所以一定要結合實際場景。

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