策略模式(Strategy Pattern):定義一系列算法類,將每個算法封裝起來,並讓它們能夠相互替換,策略模式讓算法獨立於使用它的客戶端而變化,也稱爲政策模式(Policy)。
簡而言之,策略模式是客戶端在運行時選擇某種解決方案(策略,方法,算法)來解決問題,而解決方案(策略,方法,算法)的定義與使用是分開的,即解決方案與客戶端的調用中間有個類能夠針對不一樣的問題,採起不一樣的解決方案,該中間類中持有一個對策略類接口的引用實例,用於定義所採用的策略。git
本文涉及的代碼在github上,點擊 連接 可查看源碼。
文章會用兩種方式來實現策略模式,一種是Java8以前經過本身寫接口的方式實現策略模式,另外一種是採用Java8提供的函數式接口來實現策略模式,也用了lambda表達式,使代碼看起來更加簡潔,使咱們能更專一於業務邏輯的處理。github
文章所用的場景是這樣的:根據快遞包裹的重量計算快遞公司的郵費,不一樣的快遞公司有不一樣的郵費計算方式,如京東,申通,圓通等快遞公司的郵費計算都是不同的。算法
策略模式須要一個策略接口,定義一個待實現的方法,不一樣的解決方案(方法)實現該策略接口,以實現不一樣的解決方法,接着爲了讓客戶端和策略解耦,即解決方案的定義與使用是分開的,咱們須要有一箇中間類,咱們先看一下上面提到的場景下的UML類圖:
app
CalculateStrategy是策略接口,有一個待實現的calculate方法,計算郵費,根據傳入的包裹重量,返回計算好的郵費。JdCalculateStrategy、StoCalculateStrategy、YtoCalculateStrategy分別爲京東、申通、圓通不一樣的計算郵費方式,實現了CalculateStrategy接口的calculate方法。ide
CalculateStrategy策略接口代碼以下:函數
public interface CalculateStrategy { Double calculate(Integer weight); }
JdCalculateStrategy類:this
public class JdCalculateStrategy implements CalculateStrategy { @Override public Double calculate(Integer weight) { return 10 + weight * 1.2; } }
StoCalculateStrategy類:spa
public class StoCalculateStrategy implements CalculateStrategy { @Override public Double calculate(Integer weight) { return 12 + weight * 0.8; } }
YtoCalculateStrategy類:code
public class YtoCalculateStrategy implements CalculateStrategy { @Override public Double calculate(Integer weight) { return 8 + weight * 1.5; } }
咱們能夠看到不一樣的快遞公司有不一樣的策略來計算郵費。爲了不在日後郵費計算須要修改的時候,也須要修改客戶調用方代碼,也爲了進一步解耦,咱們須要有一箇中間類,來把策略封裝起來,持有策略接口。
中間類CalculateContext代碼以下:對象
public class CalculateContext { //持有策略接口 private CalculateStrategy calculateStrategy; public void setCalculateStrategy(CalculateStrategy calculateStrategy) { this.calculateStrategy = calculateStrategy; } public Double calculate(Integer weigth) { return calculateStrategy.calculate(weigth); } }
在運行時選擇某種解決方案,客戶端代碼以下:
public class Client { public static void main(String[] args) { Integer weight = 15; CalculateContext context = new CalculateContext(); CalculateStrategy calculateStrategy = new JdCalculateStrategy(); context.setCalculateStrategy(calculateStrategy); System.out.println("運行時指定策略,計算郵費以下:" + context.calculate(weight)); } }
仔細思考,不難發現,不一樣的策略實際上是不一樣的方法,那麼有沒有一種辦法,在運行時要選擇某種方法,咱們能夠直接提供方法呢?固然不是if else 不一樣分支裏面調用咱們寫好的不一樣方法啦,這樣的話代碼結構比較亂,高度耦合了,分支也比較多,這裏採用的是Java8提供的一個內置函數式接口:
@FunctionalInterface public interface Function<T, R> { R apply(T t); }
該接口的用法,咱們能夠看apply方法,入參是T泛型類,返回值是R泛型類,仔細想一想是否是符合咱們的要求,計算郵費的方法:入參是Integer類型的包裹重量,返回參數是Double型的郵費,這裏咱們就能夠這樣用該接口,Function<Integer, Double>,計算郵費的方法用lambda表達式去實現apply接口方法。光是這樣仍是不行的,由於咱們要怎麼根據快遞公司來方便地調用lambda表達式呢?答案是:用Map集合,key是快遞公司,value是函數式接口的lambda表達式便可。
快遞公司枚舉類,做爲map的key:
public enum ParcelCompanyEnum { ZTO("中通快遞"),YTO("圓通快遞"),STO("申通快遞"),JD("京東快遞"); String name; ParcelCompanyEnum(String name) { this.name = name; } }
CalculatePostage類有一個Map成員變量,在類對象初始化的時候初始化該map,map設置lambda表達式,實現函數式接口,
public class CalculatePostage { Map<ParcelCompanyEnum, Function<Integer, Double>> map = new HashMap<>(5); { map.put(ParcelCompanyEnum.JD, this::calculateJd); map.put(ParcelCompanyEnum.STO, this::calculatSto); map.put(ParcelCompanyEnum.YTO, this::calculateYto); map.put(ParcelCompanyEnum.ZTO, this::calculateZto); } public Double calculateJd(Integer weight) { return 10 + weight * 1.2; } public Double calculatSto(Integer weight) { return 12 + weight * 0.8; } public Double calculateYto(Integer weight) { return 8 + weight * 1.5; } public Double calculateZto(Integer weight) { return 9 + weight * 1.1; } }
接着,在客戶端是這樣子來調用的:
public class Client { public static void main(String[] args) { ParcelCompanyEnum company = ParcelCompanyEnum.JD; Integer weight = 15; CalculatePostage calculatePostage = new CalculatePostage(); System.out.println("Java8 lambda + 策略模式 計算郵費:" + calculatePostage.map.get(company).apply(weight)); } }
先根據快遞公司來獲取不一樣的策略,而後傳入包裹重量,最後返回計算結果。
若是你對函數式接口、lambda表達式不熟悉或者不瞭解的話,我推薦《Java8實戰》這本書,能夠留個郵箱我發電子書。