策略模式(Strategy Pattern)詳解

文章目錄

    • 1. 什么是策略模式?
    • 2. 為什么需要策略模式?
    • 3. 策略模式的核心概念
      • 3.1 策略(Strategy)
      • 3.2 具體策略(Concrete Strategy)
      • 3.3 上下文(Context)
    • 4. 策略模式的結構
    • 5. 策略模式的基本實現
      • 5.1 基礎示例:不同的支付策略
      • 5.2 基礎示例:不同的排序策略
    • 6. 策略模式的高級實現
      • 6.1 使用枚舉實現策略模式
      • 6.2 使用Lambda表達式實現策略模式
      • 6.3 使用策略工廠
    • 7. 策略模式在Java中的實際應用
      • 7.1 Java Collections Framework中的排序
      • 7.2 線程池中的拒絕策略
      • 7.3 Spring框架中的策略模式
    • 8. 策略模式與其他設計模式的比較
      • 8.1 策略模式 vs 狀態模式
      • 8.2 策略模式 vs 命令模式
      • 8.3 策略模式 vs 工廠模式
      • 8.4 策略模式 vs 模板方法模式
    • 9. 策略模式的優缺點
      • 9.1 優點
      • 9.2 缺點
    • 10. 何時使用策略模式?
      • 10.1 實際應用場景
      • 10.2 Java中常見的策略模式應用
    • 11. 常見問題與解決方案
      • 11.1 策略選擇問題
      • 11.2 參數傳遞問題
      • 11.3 策略組合問題
    • 12. 策略模式的最佳實踐
      • 12.1 設計建議
      • 12.2 代碼示例:優化的策略實現
      • 12.3 性能考慮
    • 13. 策略模式的最佳實踐
      • 13.1 設計建議
      • 13.2 代碼示例:優化的策略實現
      • 13.3 性能考慮
    • 14. 總結
      • 14.1 核心要點
      • 14.2 常見應用場景
      • 14.3 與Java 8+的結合
      • 14.4 最終建議

1. 什么是策略模式?

策略模式是一種行為型設計模式,它定義了一系列算法,將每個算法封裝起來,并使它們可以互相替換。策略模式讓算法獨立于使用它的客戶端而變化,使得客戶端可以在不修改代碼的情況下,通過組合不同的策略對象來改變應用的行為。

策略模式的核心思想是:“定義一系列算法,封裝每一個算法,并使它們可以互換。”

在策略模式中,我們創建表示各種策略的對象和一個行為隨著策略對象改變而改變的上下文對象。策略對象改變上下文對象的執行算法。

2. 為什么需要策略模式?

在以下情況下,策略模式特別有用:

  1. 當需要在運行時選擇不同的算法時:策略模式允許客戶端在運行時選擇不同的算法實現,而不需要修改或重新編譯代碼。

  2. 當有多種類似的處理方式或算法時:例如,不同的排序算法(冒泡排序、快速排序、歸并排序等)或者不同的支付方式(信用卡支付、支付寶、微信支付等)。

  3. 當算法的使用涉及復雜的條件語句時:如果一個方法中包含了大量的條件判斷(if-else或switch)來選擇不同的算法,這會導致代碼難以維護,策略模式可以消除這些條件判斷。

  4. 當一個類定義了多種行為,并且這些行為在這個類的操作中以多個條件語句的形式出現時:策略模式可以將這些行為抽取到單獨的策略類中,使主類更加簡潔。

如果不使用策略模式,我們可能會遇到以下問題:

  • 代碼中充滿了大量的條件語句
  • 當增加新的策略時,需要修改客戶端代碼
  • 算法和使用算法的客戶端代碼耦合度高
  • 難以測試和維護

3. 策略模式的核心概念

策略模式主要包含以下幾個核心概念:

3.1 策略(Strategy)

定義所有支持的算法的公共接口。上下文使用這個接口來調用具體策略定義的算法。

3.2 具體策略(Concrete Strategy)

實現策略接口的具體算法。

3.3 上下文(Context)

維護一個對策略對象的引用,并定義一個接口來讓策略訪問它的數據。上下文對象通常接受客戶端的請求,然后委托給策略對象進行處理。

4. 策略模式的結構

策略模式通常包含以下角色:

  1. 策略(Strategy):定義所有支持的算法的公共接口
  2. 具體策略(Concrete Strategy):實現策略接口的具體算法
  3. 上下文(Context):維護一個對策略對象的引用,并定義一個接口來讓策略訪問它的數據

策略模式的 UML 類圖如下:

+----------------+        +-------------------+
|    Context     |------->|     Strategy      |
+----------------+        +-------------------+
| -strategy      |        | +algorithmIfc()   |
| +setStrategy() |        +-------------------+
| +contextMethod()|                |
+----------------+                 ||+------------+-------------+|                          |+-----------------------+   +-----------------------+| ConcreteStrategyA     |   | ConcreteStrategyB     |+-----------------------+   +-----------------------+| +algorithmIfc()       |   | +algorithmIfc()       |+-----------------------+   +-----------------------+

5. 策略模式的基本實現

5.1 基礎示例:不同的支付策略

假設我們正在開發一個在線購物系統,需要支持多種支付方式,如信用卡、支付寶和微信支付。我們可以使用策略模式來實現這個功能。

首先,定義一個策略接口:

// 支付策略接口
public interface PaymentStrategy {void pay(double amount);
}

然后,實現具體的策略類:

// 信用卡支付策略
public class CreditCardStrategy implements PaymentStrategy {private String name;private String cardNumber;private String cvv;private String dateOfExpiry;public CreditCardStrategy(String name, String cardNumber, String cvv, String dateOfExpiry) {this.name = name;this.cardNumber = cardNumber;this.cvv = cvv;this.dateOfExpiry = dateOfExpiry;}@Overridepublic void pay(double amount) {System.out.println(amount + " 元已通過信用卡支付");System.out.println("持卡人:" + name);System.out.println("卡號:" + cardNumber.substring(0, 4) + "********" + cardNumber.substring(cardNumber.length() - 4));}
}// 支付寶支付策略
public class AlipayStrategy implements PaymentStrategy {private String email;public AlipayStrategy(String email) {this.email = email;}@Overridepublic void pay(double amount) {System.out.println(amount + " 元已通過支付寶支付");System.out.println("支付寶賬號:" + email);}
}// 微信支付策略
public class WeChatPayStrategy implements PaymentStrategy {private String id;public WeChatPayStrategy(String id) {this.id = id;}@Overridepublic void pay(double amount) {System.out.println(amount + " 元已通過微信支付");System.out.println("微信ID:" + id);}
}

接下來,創建上下文類:

// 購物車(上下文)
public class ShoppingCart {private List<Item> items;public ShoppingCart() {this.items = new ArrayList<>();}public void addItem(Item item) {items.add(item);}public void removeItem(Item item) {items.remove(item);}public double calculateTotal() {double sum = 0;for (Item item : items) {sum += item.getPrice();}return sum;}public void pay(PaymentStrategy paymentStrategy) {double amount = calculateTotal();paymentStrategy.pay(amount);}
}// 商品類
public class Item {private String name;private double price;public Item(String name, double price) {this.name = name;this.price = price;}public String getName() {return name;}public double getPrice() {return price;}
}

最后,客戶端代碼:

public class StrategyPatternDemo {public static void main(String[] args) {// 創建購物車并添加商品ShoppingCart cart = new ShoppingCart();cart.addItem(new Item("書籍", 50.0));cart.addItem(new Item("電影", 20.0));cart.addItem(new Item("音樂", 10.0));// 使用信用卡支付cart.pay(new CreditCardStrategy("張三", "1234567890123456", "123", "12/25"));System.out.println("\n");// 創建新的購物車并添加商品ShoppingCart cart2 = new ShoppingCart();cart2.addItem(new Item("手機", 1000.0));cart2.addItem(new Item("耳機", 100.0));// 使用支付寶支付cart2.pay(new AlipayStrategy("zhangsan@example.com"));System.out.println("\n");// 創建新的購物車并添加商品ShoppingCart cart3 = new ShoppingCart();cart3.addItem(new Item("筆記本電腦", 5000.0));// 使用微信支付cart3.pay(new WeChatPayStrategy("zhangsan123"));}
}

輸出結果:

80.0 元已通過信用卡支付
持卡人:張三
卡號:1234********34561100.0 元已通過支付寶支付
支付寶賬號:zhangsan@example.com5000.0 元已通過微信支付
微信ID:zhangsan123

在這個例子中:

  • 策略接口PaymentStrategy定義了所有支付策略的公共接口
  • 具體策略CreditCardStrategyAlipayStrategyWeChatPayStrategy實現了不同的支付算法
  • 上下文ShoppingCart維護了一個對策略對象的引用,并定義了一個接口來讓策略訪問它的數據

通過策略模式,我們可以在不修改上下文類的情況下,輕松地添加新的支付方式,例如銀聯支付、比特幣支付等。

5.2 基礎示例:不同的排序策略

讓我們再看一個排序算法的例子,展示如何用策略模式封裝不同的排序算法。

首先,定義一個排序策略接口:

// 排序策略接口
public interface SortStrategy {void sort(int[] array);
}

然后,實現具體的排序算法策略:

// 冒泡排序策略
public class BubbleSortStrategy implements SortStrategy {@Overridepublic void sort(int[] array) {System.out.println("使用冒泡排序算法...");int n = array.length;for (int i = 0; i < n - 1; i++) {for (int j = 0; j < n - i - 1; j++) {if (array[j] > array[j + 1]) {// 交換 array[j] 和 array[j+1]int temp = array[j];array[j] = array[j + 1];array[j + 1] = temp;}}}}
}// 插入排序策略
public class InsertionSortStrategy implements SortStrategy {@Overridepublic void sort(int[] array) {System.out.println("使用插入排序算法...");int n = array.length;for (int i = 1; i < n; i++) {int key = array[i];int j = i - 1;// 將比key大的元素向右移動while (j >= 0 && array[j] > key) {array[j + 1] = array[j];j = j - 1;}array[j + 1] = key;}}
}// 快速排序策略
public class QuickSortStrategy implements SortStrategy {@Overridepublic void sort(int[] array) {System.out.println("使用快速排序算法...");quickSort(array, 0, array.length - 1);}private void quickSort(int[] array, int low, int high) {if (low < high) {int pi = partition(array, low, high);quickSort(array, low, pi - 1);quickSort(array, pi + 1, high);}}private int partition(int[] array, int low, int high) {int pivot = array[high];int i = low - 1;for (int j = low; j < high; j++) {if (array[j] <= pivot) {i++;// 交換 array[i] 和 array[j]int temp = array[i];array[i] = array[j];array[j] = temp;}}// 交換 array[i+1] 和 array[high](pivot)int temp = array[i + 1];array[i + 1] = array[high];array[high] = temp;return i + 1;}
}

接下來,創建上下文類:

// 排序上下文
public class SortContext {private SortStrategy strategy;public SortContext(SortStrategy strategy) {this.strategy = strategy;}public void setStrategy(SortStrategy strategy) {this.strategy = strategy;}public void sortArray(int[] array) {strategy.sort(array);}public void printArray(int[] array) {for (int i : array) {System.out.print(i + " ");}System.out.println();}
}

最后,客戶端代碼:

public class SortStrategyDemo {public static void main(String[] args) {// 創建待排序數組int[] array1 = {64, 34, 25, 12, 22, 11, 90};int[] array2 = {64, 34, 25, 12, 22, 11, 90};int[] array3 = {64, 34, 25, 12, 22, 11, 90};// 創建上下文并設置不同的排序策略SortContext context = new SortContext(new BubbleSortStrategy());System.out.println("原始數組:");context.printArray(array1);// 使用冒泡排序context.sortArray(array1);System.out.println("冒泡排序后:");context.printArray(array1);// 切換為插入排序context.setStrategy(new InsertionSortStrategy());context.sortArray(array2);System.out.println("插入排序后:");context.printArray(array2);// 切換為快速排序context.setStrategy(new QuickSortStrategy());context.sortArray(array3);System.out.println("快速排序后:");context.printArray(array3);}
}

輸出結果:

原始數組:
64 34 25 12 22 11 90 
使用冒泡排序算法...
冒泡排序后:
11 12 22 25 34 64 90 
使用插入排序算法...
插入排序后:
11 12 22 25 34 64 90 
使用快速排序算法...
快速排序后:
11 12 22 25 34 64 90 

在這個例子中:

  • 策略接口SortStrategy定義了所有排序策略的公共接口
  • 具體策略BubbleSortStrategyInsertionSortStrategyQuickSortStrategy實現了不同的排序算法
  • 上下文SortContext維護了一個對策略對象的引用,并定義了一個接口來讓策略訪問它的數據

通過策略模式,我們可以根據需要輕松地切換不同的排序算法,而不需要修改客戶端代碼的結構。此外,我們還可以輕松地添加新的排序算法,如歸并排序、堆排序等。

6. 策略模式的高級實現

6.1 使用枚舉實現策略模式

Java的枚舉類型可以實現接口,這使得我們可以用枚舉來實現策略模式,使代碼更加簡潔和類型安全。

// 使用枚舉實現計算器策略
public enum CalculatorStrategy {ADD {@Overridepublic double calculate(double a, double b) {return a + b;}},SUBTRACT {@Overridepublic double calculate(double a, double b) {return a - b;}},MULTIPLY {@Overridepublic double calculate(double a, double b) {return a * b;}},DIVIDE {@Overridepublic double calculate(double a, double b) {if (b == 0) {throw new ArithmeticException("除數不能為零");}return a / b;}};public abstract double calculate(double a, double b);
}// 計算器上下文
public class Calculator {public double execute(double a, double b, CalculatorStrategy strategy) {return strategy.calculate(a, b);}
}

客戶端代碼:

public class EnumStrategyDemo {public static void main(String[] args) {Calculator calculator = new Calculator();double a = 10;double b = 5;System.out.println("加法: " + calculator.execute(a, b, CalculatorStrategy.ADD));System.out.println("減法: " + calculator.execute(a, b, CalculatorStrategy.SUBTRACT));System.out.println("乘法: " + calculator.execute(a, b, CalculatorStrategy.MULTIPLY));System.out.println("除法: " + calculator.execute(a, b, CalculatorStrategy.DIVIDE));try {System.out.println("除以零: " + calculator.execute(a, 0, CalculatorStrategy.DIVIDE));} catch (ArithmeticException e) {System.out.println("錯誤: " + e.getMessage());}}
}

輸出結果:

加法: 15.0
減法: 5.0
乘法: 50.0
除法: 2.0
錯誤: 除數不能為零

使用枚舉實現策略模式有以下優點:

  • 類型安全:編譯時檢查,避免使用錯誤的策略
  • 代碼簡潔:不需要創建多個策略類
  • 單例保證:枚舉實例在JVM中保證是單例的

6.2 使用Lambda表達式實現策略模式

在Java 8及以上版本中,我們可以使用Lambda表達式和函數式接口來簡化策略模式的實現。

import java.util.function.BiFunction;// 使用函數式接口實現策略
public class FunctionalStrategyDemo {// 定義策略函數式接口@FunctionalInterfaceinterface ValidationStrategy {boolean validate(String text);}// 驗證上下文static class Validator {private final ValidationStrategy strategy;public Validator(ValidationStrategy strategy) {this.strategy = strategy;}public boolean validate(String text) {return strategy.validate(text);}}public static void main(String[] args) {// 1. 使用Lambda表達式創建具體策略ValidationStrategy isAllLowerCase = s -> s.matches("^[a-z]+$");ValidationStrategy isNumeric = s -> s.matches("^\\d+$");// 2. 創建上下文對象Validator lowerCaseValidator = new Validator(isAllLowerCase);Validator numericValidator = new Validator(isNumeric);// 3. 執行驗證String text1 = "hello";String text2 = "Hello";String text3 = "12345";System.out.println("'" + text1 + "' 全小寫? " + lowerCaseValidator.validate(text1));System.out.println("'" + text2 + "' 全小寫? " + lowerCaseValidator.validate(text2));System.out.println("'" + text1 + "' 全數字? " + numericValidator.validate(text1));System.out.println("'" + text3 + "' 全數字? " + numericValidator.validate(text3));// 4. 動態創建和使用策略System.out.println("\n使用即時創建的策略:");validateString(text1, s -> s.length() > 3, "長度大于3");validateString(text2, s -> s.startsWith("H"), "以H開頭");validateString(text3, s -> Integer.parseInt(s) > 10000, "數值大于10000");// 5. 使用標準函數式接口System.out.println("\n使用BiFunction作為策略:");BiFunction<Double, Double, Double> addition = (a, b) -> a + b;BiFunction<Double, Double, Double> multiplication = (a, b) -> a * b;System.out.println("10 + 5 = " + executeOperation(10.0, 5.0, addition));System.out.println("10 * 5 = " + executeOperation(10.0, 5.0, multiplication));}private static void validateString(String text, ValidationStrategy strategy, String description) {System.out.println("'" + text + "' " + description + "? " + strategy.validate(text));}private static Double executeOperation(Double a, Double b, BiFunction<Double, Double, Double> strategy) {return strategy.apply(a, b);}
}

輸出結果:

'hello' 全小寫? true
'Hello' 全小寫? false
'hello' 全數字? false
'12345' 全數字? true使用即時創建的策略:
'hello' 長度大于3? true
'Hello' 以H開頭? true
'12345' 數值大于10000? false使用BiFunction作為策略:
10 + 5 = 15.0
10 * 5 = 50.0

使用Lambda表達式實現策略模式有以下優點:

  • 代碼更加簡潔,減少了定義具體策略類的需要
  • 可以在使用時即時創建策略,提高了靈活性
  • 可以利用Java 8提供的標準函數式接口,如FunctionBiFunctionPredicate

6.3 使用策略工廠

當策略數量較多或者策略創建過程復雜時,我們可以使用工廠模式來管理策略的創建和選擇。

import java.util.HashMap;
import java.util.Map;// 折扣策略接口
interface DiscountStrategy {double applyDiscount(double amount);
}// 會員折扣
class MemberDiscount implements DiscountStrategy {@Overridepublic double applyDiscount(double amount) {return amount * 0.9; // 10%折扣}
}// VIP折扣
class VIPDiscount implements DiscountStrategy {@Overridepublic double applyDiscount(double amount) {return amount * 0.8; // 20%折扣}
}// 新客戶折扣
class NewCustomerDiscount implements DiscountStrategy {@Overridepublic double applyDiscount(double amount) {return amount * 0.95; // 5%折扣}
}// 節日折扣
class FestivalDiscount implements DiscountStrategy {@Overridepublic double applyDiscount(double amount) {return amount * 0.8; // 20%折扣}
}// 沒有折扣
class NoDiscount implements DiscountStrategy {@Overridepublic double applyDiscount(double amount) {return amount; // 無折扣}
}// 折扣策略工廠
class DiscountStrategyFactory {private static Map<String, DiscountStrategy> strategies = new HashMap<>();static {strategies.put("MEMBER", new MemberDiscount());strategies.put("VIP", new VIPDiscount());strategies.put("NEW", new NewCustomerDiscount());strategies.put("FESTIVAL", new FestivalDiscount());strategies.put("NONE", new NoDiscount());}public static DiscountStrategy getStrategy(String type) {if (type == null || !strategies.containsKey(type)) {throw new IllegalArgumentException("無效的折扣類型");}return strategies.get(type);}// 允許注冊新的策略public static void registerStrategy(String type, DiscountStrategy strategy) {strategies.put(type, strategy);}
}// 價格計算器上下文
class PriceCalculator {private DiscountStrategy strategy;public PriceCalculator(DiscountStrategy strategy) {this.strategy = strategy;}public void setStrategy(DiscountStrategy strategy) {this.strategy = strategy;}public double calculateFinalPrice(double price) {return strategy.applyDiscount(price);}
}

客戶端代碼:

public class StrategyFactoryDemo {public static void main(String[] args) {// 原始價格double originalPrice = 100.0;try {// 使用工廠獲取不同的折扣策略DiscountStrategy memberStrategy = DiscountStrategyFactory.getStrategy("MEMBER");DiscountStrategy vipStrategy = DiscountStrategyFactory.getStrategy("VIP");DiscountStrategy newCustomerStrategy = DiscountStrategyFactory.getStrategy("NEW");DiscountStrategy festivalStrategy = DiscountStrategyFactory.getStrategy("FESTIVAL");DiscountStrategy noDiscountStrategy = DiscountStrategyFactory.getStrategy("NONE");// 創建價格計算器PriceCalculator calculator = new PriceCalculator(memberStrategy);// 計算會員價格System.out.println("會員價格: " + calculator.calculateFinalPrice(originalPrice));// 切換為VIP策略calculator.setStrategy(vipStrategy);System.out.println("VIP價格: " + calculator.calculateFinalPrice(originalPrice));// 切換為新客戶策略calculator.setStrategy(newCustomerStrategy);System.out.println("新客戶價格: " + calculator.calculateFinalPrice(originalPrice));// 切換為節日策略calculator.setStrategy(festivalStrategy);System.out.println("節日價格: " + calculator.calculateFinalPrice(originalPrice));// 切換為無折扣策略calculator.setStrategy(noDiscountStrategy);System.out.println("原價: " + calculator.calculateFinalPrice(originalPrice));// 注冊一個新的折扣策略DiscountStrategyFactory.registerStrategy("SUPER_VIP", amount -> amount * 0.7); // 30%折扣// 使用新注冊的策略calculator.setStrategy(DiscountStrategyFactory.getStrategy("SUPER_VIP"));System.out.println("超級VIP價格: " + calculator.calculateFinalPrice(originalPrice));// 嘗試使用不存在的策略calculator.setStrategy(DiscountStrategyFactory.getStrategy("INVALID"));} catch (IllegalArgumentException e) {System.out.println("錯誤: " + e.getMessage());}}
}

輸出結果:

會員價格: 90.0
VIP價格: 80.0
新客戶價格: 95.0
節日價格: 80.0
原價: 100.0
超級VIP價格: 70.0
錯誤: 無效的折扣類型

使用策略工廠有以下優點:

  • 集中管理所有的策略對象
  • 封裝策略的創建邏輯
  • 可以動態地注冊和獲取策略
  • 避免在客戶端代碼中直接創建策略對象,降低耦合度

7. 策略模式在Java中的實際應用

策略模式在Java標準庫和流行框架中有廣泛的應用。

7.1 Java Collections Framework中的排序

java.util.Collections.sort()java.util.Arrays.sort()方法使用策略模式來支持自定義排序:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;// 學生類
class Student {private String name;private int age;private double score;public Student(String name, int age, double score) {this.name = name;this.age = age;this.score = score;}public String getName() {return name;}public int getAge() {return age;}public double getScore() {return score;}@Overridepublic String toString() {return "Student{name='" + name + "', age=" + age + ", score=" + score + "}";}
}public class JavaSortStrategyDemo {public static void main(String[] args) {// 創建學生列表List<Student> students = new ArrayList<>();students.add(new Student("張三", 20, 85.5));students.add(new Student("李四", 22, 90.0));students.add(new Student("王五", 19, 78.5));students.add(new Student("趙六", 21, 92.5));students.add(new Student("錢七", 20, 88.0));System.out.println("原始學生列表:");printStudents(students);// 按年齡排序(使用匿名內部類)Collections.sort(students, new Comparator<Student>() {@Overridepublic int compare(Student s1, Student s2) {return Integer.compare(s1.getAge(), s2.getAge());}});System.out.println("\n按年齡排序后:");printStudents(students);// 按分數排序(使用Lambda表達式)Collections.sort(students, (s1, s2) -> Double.compare(s2.getScore(), s1.getScore()));System.out.println("\n按分數降序排序后:");printStudents(students);// 按姓名排序(使用方法引用)Collections.sort(students, Comparator.comparing(Student::getName));System.out.println("\n按姓名排序后:");printStudents(students);// 組合排序策略:先按年齡,年齡相同再按分數降序Comparator<Student> ageComparator = Comparator.comparing(Student::getAge);Comparator<Student> scoreComparator = Comparator.comparing(Student::getScore).reversed();Comparator<Student> combinedComparator = ageComparator.thenComparing(scoreComparator);Collections.sort(students, combinedComparator);System.out.println("\n先按年齡,再按分數降序排序后:");printStudents(students);}private static void printStudents(List<Student> students) {for (Student student : students) {System.out.println(student);}}
}

輸出結果:

原始學生列表:
Student{name='張三', age=20, score=85.5}
Student{name='李四', age=22, score=90.0}
Student{name='王五', age=19, score=78.5}
Student{name='趙六', age=21, score=92.5}
Student{name='錢七', age=20, score=88.0}按年齡排序后:
Student{name='王五', age=19, score=78.5}
Student{name='張三', age=20, score=85.5}
Student{name='錢七', age=20, score=88.0}
Student{name='趙六', age=21, score=92.5}
Student{name='李四', age=22, score=90.0}按分數降序排序后:
Student{name='趙六', age=21, score=92.5}
Student{name='李四', age=22, score=90.0}
Student{name='錢七', age=20, score=88.0}
Student{name='張三', age=20, score=85.5}
Student{name='王五', age=19, score=78.5}按姓名排序后:
Student{name='李四', age=22, score=90.0}
Student{name='王五', age=19, score=78.5}
Student{name='張三', age=20, score=85.5}
Student{name='趙六', age=21, score=92.5}
Student{name='錢七', age=20, score=88.0}先按年齡,再按分數降序排序后:
Student{name='王五', age=19, score=78.5}
Student{name='錢七', age=20, score=88.0}
Student{name='張三', age=20, score=85.5}
Student{name='趙六', age=21, score=92.5}
Student{name='李四', age=22, score=90.0}

在這個例子中,Comparator<T>接口充當了策略接口,不同的比較器實現了不同的排序策略。Collections.sort()方法則是上下文,它接受一個列表和一個比較器,并根據比較器定義的策略對列表進行排序。

7.2 線程池中的拒絕策略

Java的ThreadPoolExecutor類使用策略模式來處理線程池飽和時的拒絕策略:

import java.util.concurrent.*;
import java.util.concurrent.ThreadPoolExecutor.AbortPolicy;
import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;
import java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy;
import java.util.concurrent.ThreadPoolExecutor.DiscardPolicy;public class ThreadPoolRejectionStrategyDemo {public static void main(String[] args) {// 測試不同的拒絕策略testRejectionStrategy(new AbortPolicy());testRejectionStrategy(new DiscardPolicy());testRejectionStrategy(new DiscardOldestPolicy());testRejectionStrategy(new CallerRunsPolicy());// 自定義拒絕策略RejectedExecutionHandler customPolicy = (r, executor) -> {System.out.println("自定義拒絕策略: 任務 " + r.toString() + " 被拒絕");// 可以記錄日志,發送通知,或者執行其他操作};testRejectionStrategy(customPolicy);}private static void testRejectionStrategy(RejectedExecutionHandler rejectionStrategy) {System.out.println("\n測試拒絕策略: " + rejectionStrategy.getClass().getSimpleName());// 創建一個容量極小的線程池ThreadPoolExecutor executor = new ThreadPoolExecutor(1, // 核心線程數2, // 最大線程數1, // 空閑線程存活時間TimeUnit.SECONDS,new ArrayBlockingQueue<>(2), // 有界隊列,容量為2Executors.defaultThreadFactory(),rejectionStrategy // 使用指定的拒絕策略);try {// 提交5個任務,超過線程池處理能力,會觸發拒絕策略for (int i = 1; i <= 5; i++) {final int taskId = i;executor.submit(() -> {try {System.out.println("執行任務 " + taskId + " 在線程: " + Thread.currentThread().getName());Thread.sleep(1000); // 模擬任務執行時間} catch (InterruptedException e) {Thread.currentThread().interrupt();}});System.out.println("提交任務 " + taskId);}} catch (Exception e) {System.out.println("異常: " + e.getMessage());} finally {executor.shutdown();try {executor.awaitTermination(5, TimeUnit.SECONDS);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}
}

輸出結果(因為涉及到多線程,每次運行輸出可能會有所不同):

測試拒絕策略: AbortPolicy
提交任務 1
提交任務 2
提交任務 3
執行任務 1 在線程: pool-1-thread-1
提交任務 4
異常: Task java.util.concurrent.FutureTask@15db9742 rejected from java.util.concurrent.ThreadPoolExecutor@6d06d69c[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0]測試拒絕策略: DiscardPolicy
提交任務 1
提交任務 2
提交任務 3
執行任務 1 在線程: pool-2-thread-1
提交任務 4
提交任務 5
執行任務 2 在線程: pool-2-thread-2
執行任務 3 在線程: pool-2-thread-1
執行任務 4 在線程: pool-2-thread-2測試拒絕策略: DiscardOldestPolicy
提交任務 1
提交任務 2
提交任務 3
執行任務 1 在線程: pool-3-thread-1
提交任務 4
提交任務 5
執行任務 3 在線程: pool-3-thread-2
執行任務 4 在線程: pool-3-thread-1
執行任務 5 在線程: pool-3-thread-2測試拒絕策略: CallerRunsPolicy
提交任務 1
提交任務 2
提交任務 3
執行任務 1 在線程: pool-4-thread-1
提交任務 4
執行任務 4 在線程: main
提交任務 5
執行任務 5 在線程: main
執行任務 2 在線程: pool-4-thread-2
執行任務 3 在線程: pool-4-thread-1測試拒絕策略: $1
提交任務 1
提交任務 2
提交任務 3
執行任務 1 在線程: pool-5-thread-1
提交任務 4
自定義拒絕策略: 任務 java.util.concurrent.FutureTask@4d7e1886 被拒絕
提交任務 5
自定義拒絕策略: 任務 java.util.concurrent.FutureTask@3cd1a2f1 被拒絕
執行任務 2 在線程: pool-5-thread-2
執行任務 3 在線程: pool-5-thread-1

在Java的線程池實現中,RejectedExecutionHandler接口充當策略接口,有四種標準實現:

  • AbortPolicy:直接拋出異常
  • DiscardPolicy:直接丟棄任務,不做任何處理
  • DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新提交被拒絕的任務
  • CallerRunsPolicy:由調用線程處理該任務

這是策略模式的一個很好的實際應用例子,允許開發者根據需要選擇不同的拒絕策略,甚至實現自己的策略。

7.3 Spring框架中的策略模式

Spring框架廣泛使用策略模式,例如在資源加載、事務管理和Bean實例化等方面。以下是一個簡單的Spring Boot示例,展示如何在Spring中應用策略模式:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Map;// 策略接口
interface NotificationStrategy {void sendNotification(String message);
}// 電子郵件通知策略
@Component("email")
class EmailNotification implements NotificationStrategy {@Overridepublic void sendNotification(String message) {System.out.println("通過電子郵件發送通知: " + message);}
}// 短信通知策略
@Component("sms")
class SMSNotification implements NotificationStrategy {@Overridepublic void sendNotification(String message) {System.out.println("通過短信發送通知: " + message);}
}// 微信通知策略
@Component("wechat")
class WeChatNotification implements NotificationStrategy {@Overridepublic void sendNotification(String message) {System.out.println("通過微信發送通知: " + message);}
}// 策略管理器
@Service
class NotificationStrategyManager {private final Map<String, NotificationStrategy> strategies;// 自動注入所有的策略實現public NotificationStrategyManager(Map<String, NotificationStrategy> strategies) {this.strategies = strategies;}public NotificationStrategy getStrategy(String strategyName) {return strategies.get(strategyName);}
}// 通知服務(上下文)
@Service
class NotificationService {private final NotificationStrategyManager strategyManager;public NotificationService(NotificationStrategyManager strategyManager) {this.strategyManager = strategyManager;}public void sendNotification(String message, String strategyName) {NotificationStrategy strategy = strategyManager.getStrategy(strategyName);if (strategy == null) {throw new IllegalArgumentException("未知的通知策略: " + strategyName);}strategy.sendNotification(message);}
}@SpringBootApplication
public class SpringStrategyPatternDemo {public static void main(String[] args) {var context = SpringApplication.run(SpringStrategyPatternDemo.class, args);// 獲取通知服務NotificationService service = context.getBean(NotificationService.class);// 使用不同的通知策略service.sendNotification("重要消息!", "email");service.sendNotification("緊急通知!", "sms");service.sendNotification("會議提醒!", "wechat");// 關閉應用SpringApplication.exit(context);}
}

輸出結果:

通過電子郵件發送通知: 重要消息!
通過短信發送通知: 緊急通知!
通過微信發送通知: 會議提醒!

在這個Spring Boot示例中:

  • NotificationStrategy接口定義了通知策略
  • 具體策略類(EmailNotificationSMSNotificationWeChatNotification)使用@Component注解,并指定bean名稱
  • NotificationStrategyManager負責管理和獲取不同的策略
  • NotificationService是上下文類,根據指定的策略名稱選擇并執行相應的策略

Spring框架使這種實現變得簡單,通過依賴注入自動管理策略對象,并提供了便捷的方法來獲取和使用它們。

8. 策略模式與其他設計模式的比較

8.1 策略模式 vs 狀態模式

相似點

  • 兩者都封裝行為到不同的類中
  • 兩者都通過組合和委托來改變對象的行為
  • 都有上下文類來管理具體行為

不同點

  • 意圖不同

    • 策略模式:封裝可互換的行為,并使用委托來決定使用哪一個
    • 狀態模式:允許對象在內部狀態改變時改變它的行為
  • 行為改變的觸發方式

    • 策略模式:由客戶端代碼顯式地選擇策略
    • 狀態模式:由上下文對象的內部狀態自動地決定使用哪個狀態對象
  • 狀態轉換

    • 策略模式:通常沒有策略對象之間的轉換,策略的選擇由客戶端決定
    • 狀態模式:狀態對象知道如何切換到另一個狀態

下面是一個簡單的例子展示兩者的區別:

// 策略模式示例
interface MoveStrategy {void move();
}class WalkStrategy implements MoveStrategy {@Overridepublic void move() {System.out.println("Walking...");}
}class RunStrategy implements MoveStrategy {@Overridepublic void move() {System.out.println("Running...");}
}class Character {private MoveStrategy moveStrategy;public void setMoveStrategy(MoveStrategy moveStrategy) {this.moveStrategy = moveStrategy;}public void move() {if (moveStrategy != null) {moveStrategy.move();}}
}// 狀態模式示例
interface MoveState {void move(CharacterWithState character);
}class WalkState implements MoveState {@Overridepublic void move(CharacterWithState character) {System.out.println("Walking...");// 可以改變狀態,例如檢測到快速移動輸入if (Math.random() > 0.7) {System.out.println("Switching to running...");character.setState(new RunState());}}
}class RunState implements MoveState {@Overridepublic void move(CharacterWithState character) {System.out.println("Running...");// 可以改變狀態,例如檢測到持續時間過長if (Math.random() > 0.3) {System.out.println("Getting tired, switching to walking...");character.setState(new WalkState());}}
}class CharacterWithState {private MoveState state;public CharacterWithState() {this.state = new WalkState(); // 初始狀態}public void setState(MoveState state) {this.state = state;}public void move() {state.move(this); // 狀態可以改變自身}
}// 演示代碼
public class StrategyVsStateDemo {public static void main(String[] args) {System.out.println("策略模式示例:");Character character = new Character();// 客戶端選擇策略character.setMoveStrategy(new WalkStrategy());character.move();character.setMoveStrategy(new RunStrategy());character.move();System.out.println("\n狀態模式示例:");CharacterWithState characterWithState = new CharacterWithState();// 狀態自動轉換for (int i = 0; i < 5; i++) {characterWithState.move();}}
}

輸出結果(因為狀態轉換有隨機因素,結果可能會有所不同):

策略模式示例:
Walking...
Running...狀態模式示例:
Walking...
Switching to running...
Running...
Getting tired, switching to walking...
Walking...
Walking...
Switching to running...
Running...

8.2 策略模式 vs 命令模式

相似點

  • 兩者都封裝行為到對象中
  • 兩者都使用組合而非繼承來增強靈活性

不同點

  • 意圖不同

    • 策略模式:定義一系列可互換的算法,使算法可以獨立于使用它的客戶而變化
    • 命令模式:將請求封裝為一個對象,從而使你可以用不同的請求對客戶進行參數化
  • 使用場景

    • 策略模式:當你需要在運行時選擇不同的算法
    • 命令模式:當你需要將操作封裝為對象,支持撤銷、排隊、日志等功能
  • 接收者

    • 策略模式:策略直接執行算法
    • 命令模式:命令通常操作一個接收者對象,接收者執行實際工作
// 策略模式示例
interface TextFormattingStrategy {String format(String text);
}class UpperCaseStrategy implements TextFormattingStrategy {@Overridepublic String format(String text) {return text.toUpperCase();}
}class LowerCaseStrategy implements TextFormattingStrategy {@Overridepublic String format(String text) {return text.toLowerCase();}
}class Formatter {private TextFormattingStrategy strategy;public void setStrategy(TextFormattingStrategy strategy) {this.strategy = strategy;}public String format(String text) {if (strategy == null) {return text;}return strategy.format(text);}
}// 命令模式示例
interface TextCommand {String execute();void undo();
}class TextEditor {private String text;public TextEditor(String text) {this.text = text;}public void setText(String text) {this.text = text;}public String getText() {return text;}public String toUpperCase() {return text.toUpperCase();}public String toLowerCase() {return text.toLowerCase();}
}class UpperCaseCommand implements TextCommand {private TextEditor editor;private String previousText;public UpperCaseCommand(TextEditor editor) {this.editor = editor;}@Overridepublic String execute() {previousText = editor.getText();String upperCaseText = editor.toUpperCase();editor.setText(upperCaseText);return upperCaseText;}@Overridepublic void undo() {editor.setText(previousText);}
}class LowerCaseCommand implements TextCommand {private TextEditor editor;private String previousText;public LowerCaseCommand(TextEditor editor) {this.editor = editor;}@Overridepublic String execute() {previousText = editor.getText();String lowerCaseText = editor.toLowerCase();editor.setText(lowerCaseText);return lowerCaseText;}@Overridepublic void undo() {editor.setText(previousText);}
}// 命令調用者
class CommandInvoker {private List<TextCommand> history = new ArrayList<>();public String executeCommand(TextCommand command) {String result = command.execute();history.add(command);return result;}public void undoLastCommand() {if (!history.isEmpty()) {TextCommand command = history.remove(history.size() - 1);command.undo();}}
}// 演示代碼
public class StrategyVsCommandDemo {public static void main(String[] args) {String text = "Hello Strategy and Command Patterns";System.out.println("原始文本: " + text);// 策略模式示例System.out.println("\n策略模式示例:");Formatter formatter = new Formatter();formatter.setStrategy(new UpperCaseStrategy());System.out.println("大寫策略: " + formatter.format(text));formatter.setStrategy(new LowerCaseStrategy());System.out.println("小寫策略: " + formatter.format(text));// 命令模式示例System.out.println("\n命令模式示例:");TextEditor editor = new TextEditor(text);CommandInvoker invoker = new CommandInvoker();System.out.println("執行大寫命令: " + invoker.executeCommand(new UpperCaseCommand(editor)));System.out.println("執行小寫命令: " + invoker.executeCommand(new LowerCaseCommand(editor)));invoker.undoLastCommand();System.out.println("撤銷后: " + editor.getText());invoker.undoLastCommand();System.out.println("再次撤銷后: " + editor.getText());}
}

輸出結果:

原始文本: Hello Strategy and Command Patterns策略模式示例:
大寫策略: HELLO STRATEGY AND COMMAND PATTERNS
小寫策略: hello strategy and command patterns命令模式示例:
執行大寫命令: HELLO STRATEGY AND COMMAND PATTERNS
執行小寫命令: hello strategy and command patterns
撤銷后: HELLO STRATEGY AND COMMAND PATTERNS
再次撤銷后: Hello Strategy and Command Patterns

8.3 策略模式 vs 工廠模式

相似點

  • 兩者都創建對象
  • 兩者都使用多態性和接口
  • 兩者都能減少條件語句的使用

不同點

  • 意圖不同

    • 策略模式:更改對象的行為
    • 工廠模式:創建對象而不指定創建哪個具體類
  • 使用時機

    • 策略模式:當你有多種算法可供選擇,并希望在運行時選擇其中之一
    • 工廠模式:當你需要創建一個對象,但事先不知道應該創建哪個具體類的對象
  • 關注點

    • 策略模式:關注行為的多樣性和可替換性
    • 工廠模式:關注對象的創建過程
// 策略模式示例
interface SortStrategy {void sort(int[] array);
}class QuickSortStrategy implements SortStrategy {@Overridepublic void sort(int[] array) {System.out.println("使用快速排序...");// 快速排序實現...}
}class MergeSortStrategy implements SortStrategy {@Overridepublic void sort(int[] array) {System.out.println("使用歸并排序...");// 歸并排序實現...}
}class ArraySorter {private SortStrategy strategy;public void setSortStrategy(SortStrategy strategy) {this.strategy = strategy;}public void sort(int[] array) {strategy.sort(array);}
}// 工廠模式示例
interface Logger {void log(String message);
}class FileLogger implements Logger {@Overridepublic void log(String message) {System.out.println("文件日志: " + message);}
}class ConsoleLogger implements Logger {@Overridepublic void log(String message) {System.out.println("控制臺日志: " + message);}
}class DatabaseLogger implements Logger {@Overridepublic void log(String message) {System.out.println("數據庫日志: " + message);}
}class LoggerFactory {public static Logger createLogger(String type) {switch (type.toLowerCase()) {case "file":return new FileLogger();case "console":return new ConsoleLogger();case "database":return new DatabaseLogger();default:throw new IllegalArgumentException("未知的日志類型: " + type);}}
}// 演示代碼
public class StrategyVsFactoryDemo {public static void main(String[] args) {// 策略模式示例System.out.println("策略模式示例:");int[] array = {5, 2, 8, 1, 9};ArraySorter sorter = new ArraySorter();sorter.setSortStrategy(new QuickSortStrategy());sorter.sort(array);sorter.setSortStrategy(new MergeSortStrategy());sorter.sort(array);// 工廠模式示例System.out.println("\n工廠模式示例:");Logger fileLogger = LoggerFactory.createLogger("file");Logger consoleLogger = LoggerFactory.createLogger("console");Logger databaseLogger = LoggerFactory.createLogger("database");fileLogger.log("這是一條文件日志消息");consoleLogger.log("這是一條控制臺日志消息");databaseLogger.log("這是一條數據庫日志消息");}
}

輸出結果:

策略模式示例:
使用快速排序...
使用歸并排序...工廠模式示例:
文件日志: 這是一條文件日志消息
控制臺日志: 這是一條控制臺日志消息
數據庫日志: 這是一條數據庫日志消息

8.4 策略模式 vs 模板方法模式

相似點

  • 兩者都定義算法的一部分,讓子類完成其他部分
  • 兩者都用于處理算法變化

不同點

  • 實現方式

    • 策略模式:使用組合和委托
    • 模板方法模式:使用繼承和方法重寫
  • 擴展點

    • 策略模式:整個算法都是可替換的
    • 模板方法模式:只有算法中的特定步驟是可替換的,而整體結構是固定的
  • 控制反轉

    • 策略模式:控制權在客戶端
    • 模板方法模式:控制權在父類
// 策略模式示例
interface CoffeeMakingStrategy {void makeCoffee();
}class AmericanoCoffeeStrategy implements CoffeeMakingStrategy {@Overridepublic void makeCoffee() {System.out.println("1. 研磨咖啡豆");System.out.println("2. 沖泡濃縮咖啡");System.out.println("3. 添加熱水");System.out.println("4. 美式咖啡制作完成");}
}class LatteStrategy implements CoffeeMakingStrategy {@Overridepublic void makeCoffee() {System.out.println("1. 研磨咖啡豆");System.out.println("2. 沖泡濃縮咖啡");System.out.println("3. 蒸牛奶");System.out.println("4. 倒入牛奶");System.out.println("5. 拿鐵制作完成");}
}class CoffeeMachine {private CoffeeMakingStrategy strategy;public void setStrategy(CoffeeMakingStrategy strategy) {this.strategy = strategy;}public void makeCoffee() {strategy.makeCoffee();}
}// 模板方法模式示例
abstract class CoffeeTemplate {// 模板方法public final void makeCoffee() {boilWater();grindCoffeeBeans();brewCoffee();addCondiments();System.out.println("咖啡制作完成");}// 這些是固定步驟private void boilWater() {System.out.println("將水煮沸");}private void grindCoffeeBeans() {System.out.println("研磨咖啡豆");}// 這些是可以由子類重寫的步驟protected abstract void brewCoffee();protected abstract void addCondiments();
}class AmericanoCoffee extends CoffeeTemplate {@Overrideprotected void brewCoffee() {System.out.println("沖泡濃縮咖啡");}@Overrideprotected void addCondiments() {System.out.println("添加熱水");}
}class LatteCoffee extends CoffeeTemplate {@Overrideprotected void brewCoffee() {System.out.println("沖泡濃縮咖啡");}@Overrideprotected void addCondiments() {System.out.println("蒸牛奶并倒入");}
}// 演示代碼
public class StrategyVsTemplateMethodDemo {public static void main(String[] args) {// 策略模式示例System.out.println("策略模式示例:");CoffeeMachine machine = new CoffeeMachine();System.out.println("制作美式咖啡:");machine.setStrategy(new AmericanoCoffeeStrategy());machine.makeCoffee();System.out.println("\n制作拿鐵:");machine.setStrategy(new LatteStrategy());machine.makeCoffee();// 模板方法模式示例System.out.println("\n模板方法模式示例:");System.out.println("制作美式咖啡:");CoffeeTemplate americano = new AmericanoCoffee();americano.makeCoffee();System.out.println("\n制作拿鐵:");CoffeeTemplate latte = new LatteCoffee();latte.makeCoffee();}
}

輸出結果:

策略模式示例:
制作美式咖啡:
1. 研磨咖啡豆
2. 沖泡濃縮咖啡
3. 添加熱水
4. 美式咖啡制作完成制作拿鐵:
1. 研磨咖啡豆
2. 沖泡濃縮咖啡
3. 蒸牛奶
4. 倒入牛奶
5. 拿鐵制作完成模板方法模式示例:
制作美式咖啡:
將水煮沸
研磨咖啡豆
沖泡濃縮咖啡
添加熱水
咖啡制作完成制作拿鐵:
將水煮沸
研磨咖啡豆
沖泡濃縮咖啡
蒸牛奶并倒入
咖啡制作完成

9. 策略模式的優缺點

9.1 優點

  1. 開閉原則:你可以在不修改上下文代碼的情況下引入新的策略,符合"對擴展開放,對修改封閉"的原則。

  2. 代碼復用:通過策略模式,可以避免重復的條件語句,將算法封裝在獨立的策略類中,這些策略可以被多個上下文復用。

  3. 可維護性:每個策略類可以獨立演化,當你修改一個策略類時,不會影響到其他策略類或上下文類。

  4. 運行時切換行為:策略可以在運行時動態替換,提供了更大的靈活性。

  5. 更好的測試性:每個策略都是獨立的,可以單獨測試,符合單一職責原則。

  6. 消除條件語句:避免了復雜的條件判斷語句,如多層if-else或switch語句。

  7. 客戶端代碼簡化:客戶端只需要知道策略接口,而不需要了解具體策略的實現細節。

9.2 缺點

  1. 增加類的數量:每個策略對應一個類,會增加系統中類的數量,使系統更加復雜。

  2. 客戶端必須了解所有策略:客戶端需要知道所有的策略類,以便選擇合適的策略,這增加了客戶端和策略之間的耦合。

  3. 策略的增加會導致對象數量的增加:如果有很多策略,將會創建大量的對象,可能會影響性能。

  4. 數據共享的問題:不同的策略可能需要不同的數據,有時候很難以優雅的方式在上下文和不同策略之間共享數據。

  5. 上下文和策略的通信開銷:策略和上下文之間可能需要通信,這可能帶來額外的開銷。

10. 何時使用策略模式?

在以下情況下,應考慮使用策略模式:

  1. 當你需要在運行時選擇不同算法的變體時:例如,不同的排序算法、不同的驗證規則、不同的價格計算方法等。

  2. 當你有多個類,它們的區別僅在于它們的行為時:你可以將不同的行為抽取到單獨的策略類中,而不是創建多個子類。

  3. 當你的類中有大量的條件語句,用于選擇不同的行為時:可以將這些條件語句的分支放入不同的策略類中,以減少條件語句的數量。

  4. 當你需要隱藏復雜算法的實現細節時:策略模式可以幫助你將復雜的算法實現細節封裝起來,對客戶端隱藏。

  5. 當算法使用的數據,客戶端不應該知道時:策略模式可以將這些數據封裝在策略類中,對客戶端隱藏。

  6. 當有多種可能的行為,且在運行時需要從中選擇一種時:例如,加密算法的選擇、壓縮算法的選擇等。

10.1 實際應用場景

  1. 支付系統:信用卡支付、支付寶支付、微信支付等不同的支付方式。

  2. 數據驗證:不同類型數據的驗證策略,如電子郵件驗證、電話號碼驗證、密碼強度驗證等。

  3. 文本排版:不同的文本排版策略,如左對齊、右對齊、居中對齊等。

  4. 圖像壓縮:不同的圖像壓縮算法,如JPEG、PNG、GIF等。

  5. 稅費計算:不同國家或地區的稅費計算方法。

  6. 路徑規劃:不同的路徑規劃算法,如最短路徑、最快路徑、最省油路徑等。

  7. 游戲AI:不同難度級別的游戲AI策略,如簡單、中等、困難。

10.2 Java中常見的策略模式應用

  1. Java集合框架中的排序Collections.sort()方法接受一個Comparator對象,用于定義排序策略。

  2. 線程池的拒絕策略ThreadPoolExecutor類的拒絕策略,如AbortPolicyCallerRunsPolicyDiscardPolicyDiscardOldestPolicy

  3. Spring框架中的資源加載策略ResourceLoader接口的不同實現,用于加載不同類型的資源。

  4. Spring Security中的認證策略:不同的認證提供者,如數據庫認證、LDAP認證、OAuth認證等。

  5. Java NIO中的通道選擇策略Selector類使用不同的策略來選擇就緒的通道。

11. 常見問題與解決方案

11.1 策略選擇問題

問題:如何在運行時動態選擇合適的策略?

解決方案

  • 使用工廠類或策略注冊表來管理和創建策略
  • 使用配置文件或數據庫來存儲策略選擇規則
  • 使用上下文信息(如用戶偏好、系統狀態等)來選擇策略
// 策略注冊表
class StrategyRegistry {private static Map<String, PaymentStrategy> strategies = new HashMap<>();static {strategies.put("CREDIT_CARD", new CreditCardStrategy("Default", "0000", "000", "01/30"));strategies.put("ALIPAY", new AlipayStrategy("default@example.com"));strategies.put("WECHAT", new WeChatPayStrategy("default_id"));}public static PaymentStrategy getStrategy(String type) {PaymentStrategy strategy = strategies.get(type);if (strategy == null) {throw new IllegalArgumentException("未知的支付類型: " + type);}return strategy;}public static void registerStrategy(String type, PaymentStrategy strategy) {strategies.put(type, strategy);}
}// 根據用戶偏好選擇策略
class UserPreferenceBasedStrategySelector {public static PaymentStrategy selectStrategy(User user) {// 檢查用戶首選支付方式String preferredPaymentMethod = user.getPreferredPaymentMethod();if (preferredPaymentMethod != null) {try {return StrategyRegistry.getStrategy(preferredPaymentMethod);} catch (IllegalArgumentException e) {// 如果首選方式無效,回退到默認方式System.out.println("用戶首選支付方式無效,使用默認方式");}}// 根據用戶歷史行為選擇if (user.hasUsedCreditCard()) {return StrategyRegistry.getStrategy("CREDIT_CARD");} else if (user.hasUsedAlipay()) {return StrategyRegistry.getStrategy("ALIPAY");} else {// 默認支付方式return StrategyRegistry.getStrategy("WECHAT");}}
}

11.2 參數傳遞問題

問題:當不同的策略需要不同的參數時,如何處理?

解決方案

  • 使用上下文對象傳遞共享數據
  • 使用策略接口方法的參數傳遞特定數據
  • 使用構建器或工廠方法創建預配置的策略
// 帶上下文的策略接口
interface TaxCalculationStrategy {double calculateTax(TaxContext context);
}// 上下文類
class TaxContext {private double income;private String country;private boolean isResident;private int dependents;private Map<String, Object> additionalData = new HashMap<>();// 構造器、getter和setter方法public void setAdditionalData(String key, Object value) {additionalData.put(key, value);}public Object getAdditionalData(String key) {return additionalData.get(key);}
}// 中國稅務策略
class ChinaTaxStrategy implements TaxCalculationStrategy {@Overridepublic double calculateTax(TaxContext context) {double income = context.getIncome();boolean isResident = context.isResident();int dependents = context.getDependents();// 計算中國稅收...return income * 0.2 - (isResident ? dependents * 1000 : 0);}
}// 美國稅務策略
class USTaxStrategy implements TaxCalculationStrategy {@Overridepublic double calculateTax(TaxContext context) {double income = context.getIncome();boolean isResident = context.isResident();int dependents = context.getDependents();// 獲取特定于美國稅收的額外數據String state = (String) context.getAdditionalData("state");boolean hasMedicalInsurance = (boolean) context.getAdditionalData("hasMedicalInsurance");// 計算美國稅收...double federalTax = income * 0.15 - (isResident ? dependents * 2000 : 0);double stateTax = "California".equals(state) ? income * 0.08 : income * 0.05;double healthCareDeduction = hasMedicalInsurance ? 1500 : 0;return federalTax + stateTax - healthCareDeduction;}
}// 稅務計算器
class TaxCalculator {private TaxCalculationStrategy strategy;public TaxCalculator(TaxCalculationStrategy strategy) {this.strategy = strategy;}public void setStrategy(TaxCalculationStrategy strategy) {this.strategy = strategy;}public double calculateTax(TaxContext context) {return strategy.calculateTax(context);}
}

客戶端代碼:

public class TaxCalculationDemo {public static void main(String[] args) {// 創建上下文TaxContext chinaContext = new TaxContext();chinaContext.setIncome(100000);chinaContext.setCountry("China");chinaContext.setResident(true);chinaContext.setDependents(2);TaxContext usContext = new TaxContext();usContext.setIncome(100000);usContext.setCountry("USA");usContext.setResident(true);usContext.setDependents(2);usContext.setAdditionalData("state", "California");usContext.setAdditionalData("hasMedicalInsurance", true);// 創建計算器并使用不同的策略TaxCalculator calculator = new TaxCalculator(new ChinaTaxStrategy());System.out.println("中國稅收: " + calculator.calculateTax(chinaContext));calculator.setStrategy(new USTaxStrategy());System.out.println("美國稅收: " + calculator.calculateTax(usContext));}
}

輸出結果:

中國稅收: 20000.0
美國稅收: 15000.0

11.3 策略組合問題

問題:當需要組合多個策略時,如何處理?

解決方案

  • 使用組合模式來創建復合策略
  • 使用裝飾器模式來動態添加策略行為
  • 使用責任鏈模式來按順序應用多個策略
// 驗證策略接口
interface ValidationStrategy {boolean validate(String input);String getErrorMessage();
}// 長度驗證策略
class LengthValidationStrategy implements ValidationStrategy {private int minLength;private int maxLength;public LengthValidationStrategy(int minLength, int maxLength) {this.minLength = minLength;this.maxLength = maxLength;}@Overridepublic boolean validate(String input) {int length = input.length();return length >= minLength && length <= maxLength;}@Overridepublic String getErrorMessage() {return "長度必須在 " + minLength + " 到 " + maxLength + " 之間";}
}// 正則表達式驗證策略
class RegexValidationStrategy implements ValidationStrategy {private String regex;private String errorMessage;public RegexValidationStrategy(String regex, String errorMessage) {this.regex = regex;this.errorMessage = errorMessage;}@Overridepublic boolean validate(String input) {return input.matches(regex);}@Overridepublic String getErrorMessage() {return errorMessage;}
}// 組合驗證策略
class CompositeValidationStrategy implements ValidationStrategy {private List<ValidationStrategy> strategies = new ArrayList<>();private String failedStrategy = null;public void addStrategy(ValidationStrategy strategy) {strategies.add(strategy);}@Overridepublic boolean validate(String input) {for (ValidationStrategy strategy : strategies) {if (!strategy.validate(input)) {failedStrategy = strategy.getErrorMessage();return false;}}return true;}@Overridepublic String getErrorMessage() {return failedStrategy;}
}// 裝飾器驗證策略
class TrimValidationDecorator implements ValidationStrategy {private ValidationStrategy wrappedStrategy;public TrimValidationDecorator(ValidationStrategy wrappedStrategy) {this.wrappedStrategy = wrappedStrategy;}@Overridepublic boolean validate(String input) {// 去除前后空格后再驗證return wrappedStrategy.validate(input.trim());}@Overridepublic String getErrorMessage() {return wrappedStrategy.getErrorMessage();}
}

客戶端代碼:

public class CompositeStrategyDemo {public static void main(String[] args) {// 創建單個策略ValidationStrategy lengthStrategy = new LengthValidationStrategy(8, 20);ValidationStrategy passwordRegexStrategy = new RegexValidationStrategy("^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=]).*$","密碼必須包含數字、小寫字母、大寫字母和特殊字符");ValidationStrategy emailRegexStrategy = new RegexValidationStrategy("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$","電子郵件格式無效");// 創建組合策略CompositeValidationStrategy passwordValidationStrategy = new CompositeValidationStrategy();passwordValidationStrategy.addStrategy(lengthStrategy);passwordValidationStrategy.addStrategy(passwordRegexStrategy);// 創建裝飾器策略ValidationStrategy trimmedEmailValidator = new TrimValidationDecorator(emailRegexStrategy);// 測試密碼驗證String password1 = "Abc123";String password2 = "Abc123@xyz";System.out.println("密碼 '" + password1 + "' 驗證結果: " + (passwordValidationStrategy.validate(password1) ? "有效" : "無效 - " + passwordValidationStrategy.getErrorMessage()));System.out.println("密碼 '" + password2 + "' 驗證結果: " + (passwordValidationStrategy.validate(password2) ? "有效" : "無效 - " + passwordValidationStrategy.getErrorMessage()));// 測試郵箱驗證String email1 = " user@example.com ";String email2 = "invalid-email";System.out.println("郵箱 '" + email1 + "' 驗證結果: " + (trimmedEmailValidator.validate(email1) ? "有效" : "無效 - " + trimmedEmailValidator.getErrorMessage()));System.out.println("郵箱 '" + email2 + "' 驗證結果: " + (trimmedEmailValidator.validate(email2) ? "有效" : "無效 - " + trimmedEmailValidator.getErrorMessage()));}
}

輸出結果:

密碼 'Abc123' 驗證結果: 有效
密碼 'Abc123@xyz' 驗證結果: 有效
郵箱 ' user@example.com ' 驗證結果: 有效
郵箱 'invalid-email' 驗證結果: 無效 - 電子郵件格式無效

12. 策略模式的最佳實踐

12.1 設計建議

  1. 使用接口定義策略:策略應該通過接口定義,以便于替換和測試。避免使用抽象類,除非有共享代碼。

  2. 保持策略接口簡單:策略接口應該盡可能簡單,通常只有一個方法,這樣更容易實現和維護。

  3. 考慮默認策略:提供一個默認策略,以防客戶端沒有指定策略或提供了無效的策略。

  4. 使策略無狀態:盡量設計無狀態的策略,使其更加可重用。如果需要狀態,確保它在策略方法的調用之間不會保留。

  5. 命名清晰:策略的名稱應當清晰地表示其意圖,例如SortStrategyValidationStrategy等。

  6. 將策略組合化:如果有多個相關的策略決策,考慮使用組合模式來創建復合策略。

12.2 代碼示例:優化的策略實現

下面是一個優化的策略模式實現,遵循最佳實踐:

// 定義一個簡單的功能接口(Java 8+)
@FunctionalInterface
interface FilterStrategy<T> {boolean test(T item);
}// 推薦項過濾器
class RecommendationFilter<T> {private FilterStrategy<T> strategy;// 1. 提供一個默認策略public RecommendationFilter() {this(item -> true); // 默認接受所有項}public RecommendationFilter(FilterStrategy<T> strategy) {this.strategy = strategy;}// 2. 允許動態更改策略public void setStrategy(FilterStrategy<T> strategy) {this.strategy = strategy;}// 3. 應用策略過濾集合public List<T> filter(List<T> items) {List<T> result = new ArrayList<>();for (T item : items) {if (strategy.test(item)) {result.add(item);}}return result;}// 4. 支持策略組合public static <T> FilterStrategy<T> and(FilterStrategy<T> strategy1, FilterStrategy<T> strategy2) {return item -> strategy1.test(item) && strategy2.test(item);}public static <T> FilterStrategy<T> or(FilterStrategy<T> strategy1, FilterStrategy<T> strategy2) {return item -> strategy1.test(item) || strategy2.test(item);}public static <T> FilterStrategy<T> not(FilterStrategy<T> strategy) {return item -> !strategy.test(item);}
}// 產品類
class Product {private String name;private double price;private double rating;private String category;public Product(String name, double price, double rating, String category) {this.name = name;this.price = price;this.rating = rating;this.category = category;}public String getName() { return name; }public double getPrice() { return price; }public double getRating() { return rating; }public String getCategory() { return category; }@Overridepublic String toString() {return String.format("%s (%.2f元, %.1f星, %s)", name, price, rating, category);}
}// 策略工具類
class ProductStrategies {// 5. 提供常用策略的靜態工廠方法public static FilterStrategy<Product> priceBelow(double maxPrice) {return product -> product.getPrice() <= maxPrice;}public static FilterStrategy<Product> minRating(double minRating) {return product -> product.getRating() >= minRating;}public static FilterStrategy<Product> inCategory(String category) {return product -> product.getCategory().equals(category);}
}

客戶端代碼:

public class StrategyBestPracticesDemo {public static void main(String[] args) {// 創建產品列表List<Product> products = Arrays.asList(new Product("手機A", 3999, 4.5, "電子產品"),new Product("手機B", 2499, 4.0, "電子產品"),new Product("筆記本電腦", 6999, 4.8, "電子產品"),new Product("耳機", 899, 4.3, "電子產品"),new Product("書籍A", 59, 4.5, "圖書"),new Product("書籍B", 39, 3.5, "圖書"),new Product("運動鞋", 499, 4.2, "服裝"),new Product("T恤", 99, 3.8, "服裝"));System.out.println("所有產品:");products.forEach(System.out::println);// 創建推薦過濾器RecommendationFilter<Product> filter = new RecommendationFilter<>();// 使用Lambda表達式定義匿名策略System.out.println("\n價格低于1000元的產品:");filter.setStrategy(product -> product.getPrice() < 1000);printFilteredProducts(filter.filter(products));// 使用預定義策略System.out.println("\n電子產品類別中評分4.0以上的產品:");FilterStrategy<Product> electronicStrategy = ProductStrategies.inCategory("電子產品");FilterStrategy<Product> highRatingStrategy = ProductStrategies.minRating(4.0);// 組合策略filter.setStrategy(RecommendationFilter.and(electronicStrategy, highRatingStrategy));printFilteredProducts(filter.filter(products));// 價格低于1000元或評分高于4.5的產品System.out.println("\n價格低于1000元或評分高于4.5的產品:");FilterStrategy<Product> lowPriceStrategy = ProductStrategies.priceBelow(1000);FilterStrategy<Product> veryHighRatingStrategy = ProductStrategies.minRating(4.5);filter.setStrategy(RecommendationFilter.or(lowPriceStrategy, veryHighRatingStrategy));printFilteredProducts(filter.filter(products));// 非電子產品且評分低于4.0的產品System.out.println("\n非電子產品且評分低于4.0的產品:");FilterStrategy<Product> nonElectronicStrategy = RecommendationFilter.not(electronicStrategy);FilterStrategy<Product> lowRatingStrategy = RecommendationFilter.not(ProductStrategies.minRating(4.0));filter.setStrategy(RecommendationFilter.and(nonElectronicStrategy, lowRatingStrategy));printFilteredProducts(filter.filter(products));}private static void printFilteredProducts(List<Product> products) {if (products.isEmpty()) {System.out.println("沒有找到符合條件的產品");} else {products.forEach(System.out::println);}}
}

輸出結果:

所有產品:
手機A (3999.00元, 4.5星, 電子產品)
手機B (2499.00元, 4.0星, 電子產品)
筆記本電腦 (6999.00元, 4.8星, 電子產品)
耳機 (899.00元, 4.3星, 電子產品)
書籍A (59.00元, 4.5星, 圖書)
書籍B (39.00元, 3.5星, 圖書)
運動鞋 (499.00元, 4.2星, 服裝)
T恤 (99.00元, 3.8星, 服裝)價格低于1000元的產品:
耳機 (899.00元, 4.3星, 電子產品)
書籍A (59.00元, 4.5星, 圖書)
書籍B (39.00元, 3.5星, 圖書)
運動鞋 (499.00元, 4.2星, 服裝)
T恤 (99.00元, 3.8星, 服裝)電子產品類別中評分4.0以上的產品:
手機A (3999.00元, 4.5星, 電子產品)
手機B (2499.00元, 4.0星, 電子產品)
筆記本電腦 (6999.00元, 4.8星, 電子產品)
耳機 (899.00元, 4.3星, 電子產品)價格低于1000元或評分高于4.5的產品:
手機A (3999.00元, 4.5星, 電子產品)
筆記本電腦 (6999.00元, 4.8星, 電子產品)
耳機 (899.00元, 4.3星, 電子產品)
書籍A (59.00元, 4.5星, 圖書)
書籍B (39.00元, 3.5星, 圖書)
運動鞋 (499.00元, 4.2星, 服裝)
T恤 (99.00元, 3.8星, 服裝)非電子產品且評分低于4.0的產品:
書籍B (39.00元, 3.5星, 圖書)
T恤 (99.00元, 3.8星, 服裝)

這個示例展示了幾個最佳實踐:

  1. 使用函數式接口簡化策略定義
  2. 提供默認策略
  3. 支持策略組合
  4. 提供靜態工廠方法創建常用策略
  5. 使用Lambda表達式創建匿名策略

12.3 性能考慮

策略模式在提供靈活性的同時,也可能帶來一些性能開銷。以下是一些優化策略模式性能的方法:

  1. 策略復用:對于無狀態的策略,可以將其實現為單例或靜態工廠方法,避免重復創建相同的策略對象。

  2. 懶加載策略:只在真正需要時才創建策略對象。

  3. 緩存策略結果:如果策略計算開銷大,可以考慮緩存結果。

  4. 適當的粒度:選擇合適的策略粒度,太細粒度可能導致類爆炸,太粗粒度可能失去靈活性。

// 緩存策略的實現示例
class CachingPricingStrategy implements PricingStrategy {private final PricingStrategy delegate;private final Map<String, Double> cache = new HashMap<>();public CachingPricingStrategy(PricingStrategy delegate) {this.delegate = delegate;}@Overridepublic double calculatePrice(Product product) {String key = product.getId();if (cache.containsKey(key)) {System.out.println("從緩存獲取價格: " + product.getName());return cache.get(key);}System.out.println("計算價格: " + product.getName());double price = delegate.calculatePrice(product);cache.put(key, price);return price;}public void clearCache() {cache.clear();}
}

13. 策略模式的最佳實踐

13.1 設計建議

  1. 使用接口定義策略:策略應該通過接口定義,以便于替換和測試。避免使用抽象類,除非有共享代碼。

  2. 保持策略接口簡單:策略接口應該盡可能簡單,通常只有一個方法,這樣更容易實現和維護。

  3. 考慮默認策略:提供一個默認策略,以防客戶端沒有指定策略或提供了無效的策略。

  4. 使策略無狀態:盡量設計無狀態的策略,使其更加可重用。如果需要狀態,確保它在策略方法的調用之間不會保留。

  5. 命名清晰:策略的名稱應當清晰地表示其意圖,例如SortStrategyValidationStrategy等。

  6. 將策略組合化:如果有多個相關的策略決策,考慮使用組合模式來創建復合策略。

13.2 代碼示例:優化的策略實現

下面是一個優化的策略模式實現,遵循最佳實踐:

// 定義一個簡單的功能接口(Java 8+)
@FunctionalInterface
interface FilterStrategy<T> {boolean test(T item);
}// 推薦項過濾器
class RecommendationFilter<T> {private FilterStrategy<T> strategy;// 1. 提供一個默認策略public RecommendationFilter() {this(item -> true); // 默認接受所有項}public RecommendationFilter(FilterStrategy<T> strategy) {this.strategy = strategy;}// 2. 允許動態更改策略public void setStrategy(FilterStrategy<T> strategy) {this.strategy = strategy;}// 3. 應用策略過濾集合public List<T> filter(List<T> items) {List<T> result = new ArrayList<>();for (T item : items) {if (strategy.test(item)) {result.add(item);}}return result;}// 4. 支持策略組合public static <T> FilterStrategy<T> and(FilterStrategy<T> strategy1, FilterStrategy<T> strategy2) {return item -> strategy1.test(item) && strategy2.test(item);}public static <T> FilterStrategy<T> or(FilterStrategy<T> strategy1, FilterStrategy<T> strategy2) {return item -> strategy1.test(item) || strategy2.test(item);}public static <T> FilterStrategy<T> not(FilterStrategy<T> strategy) {return item -> !strategy.test(item);}
}// 產品類
class Product {private String name;private double price;private double rating;private String category;public Product(String name, double price, double rating, String category) {this.name = name;this.price = price;this.rating = rating;this.category = category;}public String getName() { return name; }public double getPrice() { return price; }public double getRating() { return rating; }public String getCategory() { return category; }@Overridepublic String toString() {return String.format("%s (%.2f元, %.1f星, %s)", name, price, rating, category);}
}// 策略工具類
class ProductStrategies {// 5. 提供常用策略的靜態工廠方法public static FilterStrategy<Product> priceBelow(double maxPrice) {return product -> product.getPrice() <= maxPrice;}public static FilterStrategy<Product> minRating(double minRating) {return product -> product.getRating() >= minRating;}public static FilterStrategy<Product> inCategory(String category) {return product -> product.getCategory().equals(category);}
}

客戶端代碼:

public class StrategyBestPracticesDemo {public static void main(String[] args) {// 創建產品列表List<Product> products = Arrays.asList(new Product("手機A", 3999, 4.5, "電子產品"),new Product("手機B", 2499, 4.0, "電子產品"),new Product("筆記本電腦", 6999, 4.8, "電子產品"),new Product("耳機", 899, 4.3, "電子產品"),new Product("書籍A", 59, 4.5, "圖書"),new Product("書籍B", 39, 3.5, "圖書"),new Product("運動鞋", 499, 4.2, "服裝"),new Product("T恤", 99, 3.8, "服裝"));System.out.println("所有產品:");products.forEach(System.out::println);// 創建推薦過濾器RecommendationFilter<Product> filter = new RecommendationFilter<>();// 使用Lambda表達式定義匿名策略System.out.println("\n價格低于1000元的產品:");filter.setStrategy(product -> product.getPrice() < 1000);printFilteredProducts(filter.filter(products));// 使用預定義策略System.out.println("\n電子產品類別中評分4.0以上的產品:");FilterStrategy<Product> electronicStrategy = ProductStrategies.inCategory("電子產品");FilterStrategy<Product> highRatingStrategy = ProductStrategies.minRating(4.0);// 組合策略filter.setStrategy(RecommendationFilter.and(electronicStrategy, highRatingStrategy));printFilteredProducts(filter.filter(products));// 價格低于1000元或評分高于4.5的產品System.out.println("\n價格低于1000元或評分高于4.5的產品:");FilterStrategy<Product> lowPriceStrategy = ProductStrategies.priceBelow(1000);FilterStrategy<Product> veryHighRatingStrategy = ProductStrategies.minRating(4.5);filter.setStrategy(RecommendationFilter.or(lowPriceStrategy, veryHighRatingStrategy));printFilteredProducts(filter.filter(products));// 非電子產品且評分低于4.0的產品System.out.println("\n非電子產品且評分低于4.0的產品:");FilterStrategy<Product> nonElectronicStrategy = RecommendationFilter.not(electronicStrategy);FilterStrategy<Product> lowRatingStrategy = RecommendationFilter.not(ProductStrategies.minRating(4.0));filter.setStrategy(RecommendationFilter.and(nonElectronicStrategy, lowRatingStrategy));printFilteredProducts(filter.filter(products));}private static void printFilteredProducts(List<Product> products) {if (products.isEmpty()) {System.out.println("沒有找到符合條件的產品");} else {products.forEach(System.out::println);}}
}

輸出結果:

所有產品:
手機A (3999.00元, 4.5星, 電子產品)
手機B (2499.00元, 4.0星, 電子產品)
筆記本電腦 (6999.00元, 4.8星, 電子產品)
耳機 (899.00元, 4.3星, 電子產品)
書籍A (59.00元, 4.5星, 圖書)
書籍B (39.00元, 3.5星, 圖書)
運動鞋 (499.00元, 4.2星, 服裝)
T恤 (99.00元, 3.8星, 服裝)價格低于1000元的產品:
耳機 (899.00元, 4.3星, 電子產品)
書籍A (59.00元, 4.5星, 圖書)
書籍B (39.00元, 3.5星, 圖書)
運動鞋 (499.00元, 4.2星, 服裝)
T恤 (99.00元, 3.8星, 服裝)電子產品類別中評分4.0以上的產品:
手機A (3999.00元, 4.5星, 電子產品)
手機B (2499.00元, 4.0星, 電子產品)
筆記本電腦 (6999.00元, 4.8星, 電子產品)
耳機 (899.00元, 4.3星, 電子產品)價格低于1000元或評分高于4.5的產品:
手機A (3999.00元, 4.5星, 電子產品)
筆記本電腦 (6999.00元, 4.8星, 電子產品)
耳機 (899.00元, 4.3星, 電子產品)
書籍A (59.00元, 4.5星, 圖書)
書籍B (39.00元, 3.5星, 圖書)
運動鞋 (499.00元, 4.2星, 服裝)
T恤 (99.00元, 3.8星, 服裝)非電子產品且評分低于4.0的產品:
書籍B (39.00元, 3.5星, 圖書)
T恤 (99.00元, 3.8星, 服裝)

這個示例展示了幾個最佳實踐:

  1. 使用函數式接口簡化策略定義
  2. 提供默認策略
  3. 支持策略組合
  4. 提供靜態工廠方法創建常用策略
  5. 使用Lambda表達式創建匿名策略

13.3 性能考慮

策略模式在提供靈活性的同時,也可能帶來一些性能開銷。以下是一些優化策略模式性能的方法:

  1. 策略復用:對于無狀態的策略,可以將其實現為單例或靜態工廠方法,避免重復創建相同的策略對象。

  2. 懶加載策略:只在真正需要時才創建策略對象。

  3. 緩存策略結果:如果策略計算開銷大,可以考慮緩存結果。

  4. 適當的粒度:選擇合適的策略粒度,太細粒度可能導致類爆炸,太粗粒度可能失去靈活性。

// 緩存策略的實現示例
class CachingPricingStrategy implements PricingStrategy {private final PricingStrategy delegate;private final Map<String, Double> cache = new HashMap<>();public CachingPricingStrategy(PricingStrategy delegate) {this.delegate = delegate;}@Overridepublic double calculatePrice(Product product) {String key = product.getId();if (cache.containsKey(key)) {System.out.println("從緩存獲取價格: " + product.getName());return cache.get(key);}System.out.println("計算價格: " + product.getName());double price = delegate.calculatePrice(product);cache.put(key, price);return price;}public void clearCache() {cache.clear();}
}

14. 總結

策略模式是一種強大且靈活的設計模式,它允許在運行時動態選擇算法,使算法的變化獨立于使用算法的客戶端。這種模式特別適合于需要在多種算法中進行選擇的場景,例如排序、驗證、格式化和支付處理等。

14.1 核心要點

  1. 封裝算法:策略模式將每個算法封裝在獨立的類中,使其可以獨立于客戶端而變化。

  2. 可替換性:通過定義公共接口,策略可以在運行時互相替換,而不需要修改使用它們的代碼。

  3. 消除條件語句:策略模式有助于消除復雜的條件語句,使代碼更加清晰和可維護。

  4. 遵循開閉原則:可以添加新的策略而不修改現有代碼,符合"對擴展開放,對修改封閉"的原則。

  5. 組合優于繼承:策略模式使用組合而非繼承來實現算法的變化,更加靈活。

14.2 常見應用場景

策略模式在實際應用中非常廣泛,包括但不限于:

  • 排序算法:根據不同需求選擇不同的排序算法
  • 支付處理:處理不同的支付方式
  • 驗證規則:應用不同的驗證規則和條件
  • 數據壓縮:選擇不同的壓縮算法
  • 路由算法:選擇不同的路由算法計算路徑
  • 文本格式化:應用不同的文本格式化規則
  • 價格計算:根據不同條件計算價格和折扣

14.3 與Java 8+的結合

隨著Java 8引入Lambda表達式和函數式接口,策略模式變得更加簡潔和強大。你可以使用Lambda表達式創建策略,而不需要定義多個具體策略類,這使得策略模式更容易使用和維護。

例如,使用ComparatorPredicateFunction等函數式接口,你可以輕松地創建和組合各種策略,而不需要編寫大量的樣板代碼。

14.4 最終建議

  • 適時使用:策略模式是一種強大的工具,但不是萬能的。在引入策略模式之前,考慮是否真的需要算法的動態變化。
  • 減少復雜性:避免過度使用策略模式,這可能導致系統中類的數量過多。
  • 結合其他模式:策略模式通常與工廠模式、命令模式等一起使用,以提供更完整的解決方案。

通過恰當地使用策略模式,你可以創建更加靈活、可維護和可擴展的應用程序,有效地處理算法的變化,同時保持系統的整體結構簡潔清晰。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/903085.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/903085.shtml
英文地址,請注明出處:http://en.pswp.cn/news/903085.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

在 Vue3 中封裝的 Axios 實例中,若需要為部分接口提供手動取消請求的功能

核心思路 封裝接口時返回 Promise 和 abort 方法&#xff1a; 為需要支持取消的接口返回一個對象&#xff0c;包含 promise 和 abort 方法&#xff0c;用戶可通過 abort 主動中斷請求。使用 AbortController 或 CancelToken&#xff1a; 推薦 AbortController&#xff08;瀏覽…

Flink介紹——實時計算核心論文之Dataflow論文詳解

引入 在過去的幾篇文章里&#xff0c;我們看到了大數據的流式處理系統是如何一步一步進化的。從最早出現的S4&#xff0c;到能夠做到“至少一次”處理的Storm&#xff0c;最后是能夠做到“正好一次”數據處理的MillWheel。我們會發現&#xff0c;這些流式處理框架&#xff0c;…

Python自動化解決滑塊驗證碼的最佳實踐

1. 引言&#xff1a;滑塊驗證碼的挑戰與自動化需求 滑塊驗證碼&#xff08;Slider CAPTCHA&#xff09;是當前互聯網廣泛使用的反爬機制之一&#xff0c;它要求用戶手動拖動滑塊到指定位置以完成驗證。這種驗證方式可以有效阻止簡單的自動化腳本&#xff0c;但對爬蟲開發者來說…

路由與OSPF學習

【路由是跨網段通訊的必要條件】 路由指的是在網絡中&#xff0c;數據包從源主機傳輸到目的主機的路徑選擇過程。 路由通常涉及以下幾個關鍵元素&#xff1a; 1.路由器&#xff1a;是一種網絡設備&#xff0c;負責將數據包從一個網絡傳輸到另一個網絡。路由器根據路由表來決定…

(done) 吳恩達版提示詞工程 5. 推理 (情緒分類,控制輸出格式,輸出 JSON,集成多個任務,文本主題推斷和索引,主題內容提醒)

url: https://www.bilibili.com/video/BV1Z14y1Z7LJ?spm_id_from333.788.videopod.episodes&vd_source7a1a0bc74158c6993c7355c5490fc600&p2 別人的筆記 url: https://zhuanlan.zhihu.com/p/626966526 5. 推理任務&#xff08;Inferring&#xff09; 這個視頻是關于…

MySQL VS SQL Server:優缺點全解析

數據庫選型、企業協作、技術生態、云數據庫 1.1 MySQL優缺點分析 優點 開源免費 社區版完全免費&#xff0c;適合預算有限的企業 允許修改源碼定制功能&#xff08;需遵守GPL協議&#xff09; 跨平臺兼容性 支持Windows/Linux/macOS&#xff0c;適配混合環境部署 云服務商…

Pycharm 代理配置

Pycharm 代理配置 文章目錄 Pycharm 代理配置1. 設置系統代理1.1 作用范圍1.2 使用場景1.3 設置步驟 2. 設置 python 運行/調試代理2.1 作用范圍2.2 使用場景2.3 設置步驟 Pycharm 工具作為一款強大的 IDE&#xff0c;其代理配置在實際開發中也是必不可少的&#xff0c;下面介紹…

maven打包時配置多環境參數

1. pom配置 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.…

國產三維CAD皇冠CAD在機械及汽車零部件設計建模教程:斜滑動軸承

在線解讀『斜滑動軸承』的三維建模流程&#xff0c;講解布爾運算、旋轉凸臺/基體、異型導向孔、裝飾螺紋線等操作技巧&#xff0c;一起來皇冠CAD&#xff08;CrownCAD&#xff09;直播間學習制作步驟吧&#xff01; 斜滑動軸承憑借其獨特的工作原理和廣泛的應用領域&#xff0c…

linux(操作系統概述和虛擬機的安裝)

1.操作系統 一、主流服務器操作系統 Windows server 市場地位&#xff1a;適合傳統企業&#xff08;主要以中小型企業、金融機構和教育機構為主&#xff09; 核心特點&#xff1a; 企業級功能&#xff1a;活動目錄、組策略、IIS/Web服務器、Exchange郵件服務 易用性&#xff1a…

鴻蒙生態新利器:華為ArkUI-X混合開發框架深度解析

鴻蒙生態新利器&#xff1a;華為ArkUI-X混合開發框架深度解析 作者&#xff1a;王老漢 | 鴻蒙生態開發者 | 2025年4月 &#x1f4e2; 前言&#xff1a;開發者們的新機遇 各位鴻蒙開發者朋友們&#xff0c;是否還在為多平臺開發重復造輪子而苦惱&#xff1f;今天給大家介紹一位…

數據結構初階:二叉樹(四)

概述&#xff1a;本篇博客主要介紹鏈式結構二叉樹的實現。 目錄 1.實現鏈式結構二叉樹 1.1 二叉樹的頭文件&#xff08;tree.h&#xff09; 1.2 創建二叉樹 1.3 前中后序遍歷 1.3.1 遍歷規則 1.3.1.1 前序遍歷代碼實現 1.3.1.2 中序遍歷代碼實現 1.3.1.3 后序遍歷代…

Electron Forge【實戰】桌面應用 —— AI聊天(下)

此為系列教程&#xff0c;需先完成 Electron Forge【實戰】桌面應用 —— AI聊天&#xff08;上&#xff09;Electron Forge【實戰】桌面應用 —— AI聊天&#xff08;中&#xff09; 會話列表按更新時間倒序加載 src/db.ts db.version(1).stores({// 主鍵為id&#xff0c;且…

[架構之美]Ubuntu源碼部署APISIX全流程詳解(含避坑指南)

[架構之美]Ubuntu源碼部署APISIX全流程詳解(含避坑指南) 一、離線安裝場景需求分析 1.1 典型應用場景 金融/政務內網環境生產環境安全合規要求邊緣計算節點部署1.2 離線安裝難點 #mermaid-svg-B25djI0XquaOb1HM {font-family:"trebuchet ms",verdana,arial,sans-s…

多頭注意力(Multi?Head Attention)

1. 多頭注意力&#xff08;Multi?Head Attention&#xff09;原理 設輸入序列表示為矩陣 X ∈ R B L d model X\in\mathbb{R}^{B\times L\times d_{\text{model}}} X∈RBLdmodel?&#xff0c;其中 B B B&#xff1a;批大小&#xff08;batch size&#xff09;&#xff0c…

系列位置效應——AI與思維模型【80】

一、定義 系列位置效應思維模型是指在一系列事物或信息的呈現過程中&#xff0c;人們對于處于系列開頭和結尾部分的項目的記憶效果優于中間部分項目的現象。具體而言&#xff0c;開頭部分的記憶優勢被稱為首因效應&#xff0c;結尾部分的記憶優勢被稱為近因效應。這種效應反映…

MyBatis XML 配置完整示例(含所有核心配置項)

MyBatis XML 配置完整示例&#xff08;含所有核心配置項&#xff09; 1. 完整 mybatis-config.xml 配置文件 <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""htt…

電商數據中臺架構:淘寶 API 實時采集與多源數據融合技術拆解

引言 在當今競爭激烈的電商領域&#xff0c;數據已成為企業決策和業務發展的核心驅動力。電商數據中臺能夠整合和管理企業內外部的各種數據&#xff0c;為業務提供有力支持。其中&#xff0c;淘寶 API 實時采集與多源數據融合技術是數據中臺架構中的關鍵部分。本文將深入探討這…

ubuntu22.04部署Snipe-IT

文章目錄 參考鏈接一、寫在前二、安裝操作系統三、安裝 PHP四、下載 Snipe-IT五、安裝依賴六、安裝數據庫并創建用戶七、安裝 Snipe-IT八、安裝 Nginx九、Web 繼續安裝 Snipe-IT補充&#xff1a;20250427補充&#xff1a; 最后 參考鏈接 How to Install Snipe-IT on Ubuntu 22…

圖論---Bellman-Ford算法

適用場景&#xff1a;有邊數限制 ->&#xff08;有負環也就沒影響了&#xff09;&#xff0c;存在負權邊&#xff0c;O( n * m )&#xff1b; 有負權回路時有的點距離會是負無窮&#xff0c;因此最短路存在的話就說明沒有負權回路。 從1號點經過不超過k條邊到每個點的距離…