知識庫 / Spring RSS 訂閱

Java策略設計模式

Spring
HongKong
4
02:40 PM · Dec 06 ,2025

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() 方法只是對應用一個 Discounterthis 的結果的抽象。它使用內置的函數式 apply() 來實現此目的。

現在,讓我們嘗試累積地將多個 Discounter 應用到金額上。我們將使用函數式 reduce() 和我們的 combine()

Discounter combinedDiscounter = discounters
  .stream()
  .reduce(v -> v, Discounter::combine);

combinedDiscounter.apply(...);

請特別注意第一個 reduce 參數。當未提供折扣時,我們需要返回未改變的值。這可以通過將身份函數作為默認折扣器來實現。

這是一個有用的、比標準迭代更簡潔的替代方案。考慮到我們從盒子裏獲得的方法,它也為我們提供了大量免費的功能。

4. 結論

在本文中,我們解釋了策略模式,並演示瞭如何使用 lambda 表達式以更簡潔的方式來實現它。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.