1. 介紹
本文將探討如何在 Java 8 中實現策略模式。
首先,我們將概述該模式,並解釋其在較早版本的 Java 中是如何傳統上實現的。
接下來,我們將再次嘗試實現該模式,但這次使用 Java 8 的 Lambda 表達式,從而減少代碼的冗餘。
2. 策略模式
本質上,策略模式允許我們在運行時改變算法的行為。
通常,我們首先會創建一個接口,用於應用算法,然後為每個可能的算法實現它多次。
假設我們有要求根據購買是聖誕節、復活節還是新年,來應用不同的折扣類型。首先,讓我們創建一個Discounter接口,該接口將由我們的策略實現:
public interface Discounter {
BigDecimal applyDiscount(BigDecimal amount);
}
讓我們假設我們希望在復活節和聖誕節時分別應用50%的折扣和10%的折扣。讓我們為每種策略實現我們的接口:
public static class EasterDiscounter implements Discounter {
@Override
public BigDecimal applyDiscount(final BigDecimal amount) {
return amount.multiply(BigDecimal.valueOf(0.5));
}
}
public static class ChristmasDiscounter implements Discounter {
@Override
public BigDecimal applyDiscount(final BigDecimal amount) {
return amount.multiply(BigDecimal.valueOf(0.9));
}
}
最後,我們嘗試在一個測試中應用一種策略:
Discounter easterDiscounter = new EasterDiscounter();
BigDecimal discountedValue = easterDiscounter
.applyDiscount(BigDecimal.valueOf(100));
assertThat(discountedValue)
.isEqualByComparingTo(BigDecimal.valueOf(50));這段代碼運行效果良好,但問題在於需要為每個策略創建具體的類,這可能會帶來一些麻煩。另一種方法是使用匿名內聯類型,但這種方法仍然比較冗長,而且並沒有比之前的解決方案更方便:
Discounter easterDiscounter = new Discounter() {
@Override
public BigDecimal applyDiscount(final BigDecimal amount) {
return amount.multiply(BigDecimal.valueOf(0.5));
}
};
3. 利用 Java 8
自 Java 8 發佈以來,lambda 表達式的引入使得匿名內類型在某種程度上變得多餘。這意味着使用策略模式現在更加簡潔和容易。
此外,函數式編程的聲明式風格使我們能夠實現以前不可能實現的模式。
3.1. 減少代碼冗餘
讓我們嘗試創建一個內聯 EasterDiscounter,這次只使用 lambda 表達式:
Discounter easterDiscounter = amount -> amount.multiply(BigDecimal.valueOf(0.5));
<p>如我們所見,我們的代碼現在更加簡潔易於維護,實現了與之前相同的效果,但僅需一行代碼。本質上,<strong >lambda 表達式可以被視為匿名內聯類型的替代方案</strong>。</p>
<p>這種優勢在我們需要在一行代碼中聲明更多的<em >折扣器</em>時尤為明顯:</p>
List<Discounter> discounters = newArrayList(
amount -> amount.multiply(BigDecimal.valueOf(0.9)),
amount -> amount.multiply(BigDecimal.valueOf(0.8)),
amount -> amount.multiply(BigDecimal.valueOf(0.5))
);當我們想要定義大量的 折扣商 時,我們可以將它們全部靜態地聲明在一個地方。Java 8 甚至允許我們在接口中定義靜態方法,如果需要的話。
因此,與其在實際類或匿名內部類型之間做出選擇,不如在一個類中創建所有 lambda 表達式:
public interface Discounter {
BigDecimal applyDiscount(BigDecimal amount);
static Discounter christmasDiscounter() {
return amount -> amount.multiply(BigDecimal.valueOf(0.9));
}
static Discounter newYearDiscounter() {
return amount -> amount.multiply(BigDecimal.valueOf(0.8));
}
static Discounter easterDiscounter() {
return amount -> amount.multiply(BigDecimal.valueOf(0.5));
}
}
正如我們所見,我們用相對較少的代碼就取得了很大的進展。
3.2. 利用函數合成
讓我們修改我們的 Discounter 接口使其擴展 UnaryOperator 接口,然後添加一個 combine() 方法:
public interface Discounter extends UnaryOperator<BigDecimal> {
default Discounter combine(Discounter after) {
return value -> after.apply(this.apply(value));
}
}本質上,我們正在重構我們的 Discounter,並利用了應用折扣的函數將 BigDecimal 實例轉換為另一個 BigDecimal 實例這一特性,從而可以訪問預定義的. 由於 UnaryOperator 帶有 apply() 方法,我們可以直接用它替換 applyDiscount。
combine() 方法只是對應用一個 Discounter 到 this 的結果的抽象。它使用內置的函數式 apply() 來實現此目的。
現在,讓我們嘗試累積地將多個 Discounter 應用到金額上。我們將使用函數式 reduce() 和我們的 combine()。
Discounter combinedDiscounter = discounters
.stream()
.reduce(v -> v, Discounter::combine);
combinedDiscounter.apply(...);請特別注意第一個 reduce 參數。當未提供折扣時,我們需要返回未改變的值。這可以通過將身份函數作為默認折扣器來實現。
這是一個有用的、比標準迭代更簡潔的替代方案。考慮到我們從盒子裏獲得的方法,它也為我們提供了大量免費的功能。
4. 結論
在本文中,我們解釋了策略模式,並演示瞭如何使用 lambda 表達式以更簡潔的方式來實現它。