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

一、概念以及背景

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

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

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

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

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

二、策略模式

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


CalculateStrategy是策略接口,有一個待實現的calculate方法,計算郵費,根據傳入的包裹重量,返回計算好的郵費。JdCalculateStrategy、StoCalculateStrategy、YtoCalculateStrategy分別爲京東、申通、圓通不同的計算郵費方式,實現了CalculateStrategy接口的calculate方法。

CalculateStrategy策略接口代碼如下:

public interface CalculateStrategy {
    Double calculate(Integer weight);
}

JdCalculateStrategy類:

public class JdCalculateStrategy implements CalculateStrategy {
    @Override
    public Double calculate(Integer weight) {
        return 10 + weight * 1.2;
    }
}

StoCalculateStrategy類:

public class StoCalculateStrategy implements CalculateStrategy {
    @Override
    public Double calculate(Integer weight) {
        return 12 + weight * 0.8;
    }
}

YtoCalculateStrategy類:

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

三、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實戰》這本書,可以留個郵箱我發電子書。

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

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