策略模式&lambda重構策略模式

1、概念以及背景

策略模式(Strategy Pattern):定義一系列算法類,將每個算法封裝起來,並讓它們能夠相互替換,策略模式讓算法獨立於使用它的客戶端而變化,也稱爲政策模式(Policy)。

簡而言之,策略模式是客戶端在運行時選擇某種解決方案(策略,方法,算法)來解決問題,而解決方案(策略,方法,算法)的定義與使用是分開的,即解決方案與客戶端的調用中間有個類能夠針對不一樣的問題,採起不一樣的解決方案,該中間類中持有一個對策略類接口的引用實例,用於定義所採用的策略。git

本文涉及的代碼在github上,點擊 連接 可查看源碼。

文章會用兩種方式來實現策略模式,一種是Java8以前經過本身寫接口的方式實現策略模式,另外一種是採用Java8提供的函數式接口來實現策略模式,也用了lambda表達式,使代碼看起來更加簡潔,使咱們能更專一於業務邏輯的處理。github

文章所用的場景是這樣的:根據快遞包裹的重量計算快遞公司的郵費,不一樣的快遞公司有不一樣的郵費計算方式,如京東,申通,圓通等快遞公司的郵費計算都是不同的。算法

2、策略模式

策略模式須要一個策略接口,定義一個待實現的方法,不一樣的解決方案(方法)實現該策略接口,以實現不一樣的解決方法,接着爲了讓客戶端和策略解耦,即解決方案的定義與使用是分開的,咱們須要有一箇中間類,咱們先看一下上面提到的場景下的UML類圖:
UML類圖.jpgapp

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));
    }
}

3、lambda重構策略模式

仔細思考,不難發現,不一樣的策略實際上是不一樣的方法,那麼有沒有一種辦法,在運行時要選擇某種方法,咱們能夠直接提供方法呢?固然不是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實戰》這本書,能夠留個郵箱我發電子書。

3、拓展Java8提供的內置函數式接口

內置函數式接口

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息