策略模式(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. 为什么需要策略模式?
在以下情况下,策略模式特别有用:
-
当需要在运行时选择不同的算法时:策略模式允许客户端在运行时选择不同的算法实现,而不需要修改或重新编译代码。
-
当有多种类似的处理方式或算法时:例如,不同的排序算法(冒泡排序、快速排序、归并排序等)或者不同的支付方式(信用卡支付、支付宝、微信支付等)。
-
当算法的使用涉及复杂的条件语句时:如果一个方法中包含了大量的条件判断(if-else或switch)来选择不同的算法,这会导致代码难以维护,策略模式可以消除这些条件判断。
-
当一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现时:策略模式可以将这些行为抽取到单独的策略类中,使主类更加简洁。
如果不使用策略模式,我们可能会遇到以下问题:
- 代码中充满了大量的条件语句
- 当增加新的策略时,需要修改客户端代码
- 算法和使用算法的客户端代码耦合度高
- 难以测试和维护
3. 策略模式的核心概念
策略模式主要包含以下几个核心概念:
3.1 策略(Strategy)
定义所有支持的算法的公共接口。上下文使用这个接口来调用具体策略定义的算法。
3.2 具体策略(Concrete Strategy)
实现策略接口的具体算法。
3.3 上下文(Context)
维护一个对策略对象的引用,并定义一个接口来让策略访问它的数据。上下文对象通常接受客户端的请求,然后委托给策略对象进行处理。
4. 策略模式的结构
策略模式通常包含以下角色:
- 策略(Strategy):定义所有支持的算法的公共接口
- 具体策略(Concrete Strategy):实现策略接口的具体算法
- 上下文(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
定义了所有支付策略的公共接口 - 具体策略:
CreditCardStrategy
、AlipayStrategy
和WeChatPayStrategy
实现了不同的支付算法 - 上下文:
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
定义了所有排序策略的公共接口 - 具体策略:
BubbleSortStrategy
、InsertionSortStrategy
和QuickSortStrategy
实现了不同的排序算法 - 上下文:
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提供的标准函数式接口,如
Function
、BiFunction
、Predicate
等
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
接口定义了通知策略- 具体策略类(
EmailNotification
、SMSNotification
、WeChatNotification
)使用@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 优点
-
开闭原则:你可以在不修改上下文代码的情况下引入新的策略,符合"对扩展开放,对修改封闭"的原则。
-
代码复用:通过策略模式,可以避免重复的条件语句,将算法封装在独立的策略类中,这些策略可以被多个上下文复用。
-
可维护性:每个策略类可以独立演化,当你修改一个策略类时,不会影响到其他策略类或上下文类。
-
运行时切换行为:策略可以在运行时动态替换,提供了更大的灵活性。
-
更好的测试性:每个策略都是独立的,可以单独测试,符合单一职责原则。
-
消除条件语句:避免了复杂的条件判断语句,如多层if-else或switch语句。
-
客户端代码简化:客户端只需要知道策略接口,而不需要了解具体策略的实现细节。
9.2 缺点
-
增加类的数量:每个策略对应一个类,会增加系统中类的数量,使系统更加复杂。
-
客户端必须了解所有策略:客户端需要知道所有的策略类,以便选择合适的策略,这增加了客户端和策略之间的耦合。
-
策略的增加会导致对象数量的增加:如果有很多策略,将会创建大量的对象,可能会影响性能。
-
数据共享的问题:不同的策略可能需要不同的数据,有时候很难以优雅的方式在上下文和不同策略之间共享数据。
-
上下文和策略的通信开销:策略和上下文之间可能需要通信,这可能带来额外的开销。
10. 何时使用策略模式?
在以下情况下,应考虑使用策略模式:
-
当你需要在运行时选择不同算法的变体时:例如,不同的排序算法、不同的验证规则、不同的价格计算方法等。
-
当你有多个类,它们的区别仅在于它们的行为时:你可以将不同的行为抽取到单独的策略类中,而不是创建多个子类。
-
当你的类中有大量的条件语句,用于选择不同的行为时:可以将这些条件语句的分支放入不同的策略类中,以减少条件语句的数量。
-
当你需要隐藏复杂算法的实现细节时:策略模式可以帮助你将复杂的算法实现细节封装起来,对客户端隐藏。
-
当算法使用的数据,客户端不应该知道时:策略模式可以将这些数据封装在策略类中,对客户端隐藏。
-
当有多种可能的行为,且在运行时需要从中选择一种时:例如,加密算法的选择、压缩算法的选择等。
10.1 实际应用场景
-
支付系统:信用卡支付、支付宝支付、微信支付等不同的支付方式。
-
数据验证:不同类型数据的验证策略,如电子邮件验证、电话号码验证、密码强度验证等。
-
文本排版:不同的文本排版策略,如左对齐、右对齐、居中对齐等。
-
图像压缩:不同的图像压缩算法,如JPEG、PNG、GIF等。
-
税费计算:不同国家或地区的税费计算方法。
-
路径规划:不同的路径规划算法,如最短路径、最快路径、最省油路径等。
-
游戏AI:不同难度级别的游戏AI策略,如简单、中等、困难。
10.2 Java中常见的策略模式应用
-
Java集合框架中的排序:
Collections.sort()
方法接受一个Comparator
对象,用于定义排序策略。 -
线程池的拒绝策略:
ThreadPoolExecutor
类的拒绝策略,如AbortPolicy
、CallerRunsPolicy
、DiscardPolicy
和DiscardOldestPolicy
。 -
Spring框架中的资源加载策略:
ResourceLoader
接口的不同实现,用于加载不同类型的资源。 -
Spring Security中的认证策略:不同的认证提供者,如数据库认证、LDAP认证、OAuth认证等。
-
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 设计建议
-
使用接口定义策略:策略应该通过接口定义,以便于替换和测试。避免使用抽象类,除非有共享代码。
-
保持策略接口简单:策略接口应该尽可能简单,通常只有一个方法,这样更容易实现和维护。
-
考虑默认策略:提供一个默认策略,以防客户端没有指定策略或提供了无效的策略。
-
使策略无状态:尽量设计无状态的策略,使其更加可重用。如果需要状态,确保它在策略方法的调用之间不会保留。
-
命名清晰:策略的名称应当清晰地表示其意图,例如
SortStrategy
、ValidationStrategy
等。 -
将策略组合化:如果有多个相关的策略决策,考虑使用组合模式来创建复合策略。
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星, 服装)
这个示例展示了几个最佳实践:
- 使用函数式接口简化策略定义
- 提供默认策略
- 支持策略组合
- 提供静态工厂方法创建常用策略
- 使用Lambda表达式创建匿名策略
12.3 性能考虑
策略模式在提供灵活性的同时,也可能带来一些性能开销。以下是一些优化策略模式性能的方法:
-
策略复用:对于无状态的策略,可以将其实现为单例或静态工厂方法,避免重复创建相同的策略对象。
-
懒加载策略:只在真正需要时才创建策略对象。
-
缓存策略结果:如果策略计算开销大,可以考虑缓存结果。
-
适当的粒度:选择合适的策略粒度,太细粒度可能导致类爆炸,太粗粒度可能失去灵活性。
// 缓存策略的实现示例
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 设计建议
-
使用接口定义策略:策略应该通过接口定义,以便于替换和测试。避免使用抽象类,除非有共享代码。
-
保持策略接口简单:策略接口应该尽可能简单,通常只有一个方法,这样更容易实现和维护。
-
考虑默认策略:提供一个默认策略,以防客户端没有指定策略或提供了无效的策略。
-
使策略无状态:尽量设计无状态的策略,使其更加可重用。如果需要状态,确保它在策略方法的调用之间不会保留。
-
命名清晰:策略的名称应当清晰地表示其意图,例如
SortStrategy
、ValidationStrategy
等。 -
将策略组合化:如果有多个相关的策略决策,考虑使用组合模式来创建复合策略。
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星, 服装)
这个示例展示了几个最佳实践:
- 使用函数式接口简化策略定义
- 提供默认策略
- 支持策略组合
- 提供静态工厂方法创建常用策略
- 使用Lambda表达式创建匿名策略
13.3 性能考虑
策略模式在提供灵活性的同时,也可能带来一些性能开销。以下是一些优化策略模式性能的方法:
-
策略复用:对于无状态的策略,可以将其实现为单例或静态工厂方法,避免重复创建相同的策略对象。
-
懒加载策略:只在真正需要时才创建策略对象。
-
缓存策略结果:如果策略计算开销大,可以考虑缓存结果。
-
适当的粒度:选择合适的策略粒度,太细粒度可能导致类爆炸,太粗粒度可能失去灵活性。
// 缓存策略的实现示例
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 核心要点
-
封装算法:策略模式将每个算法封装在独立的类中,使其可以独立于客户端而变化。
-
可替换性:通过定义公共接口,策略可以在运行时互相替换,而不需要修改使用它们的代码。
-
消除条件语句:策略模式有助于消除复杂的条件语句,使代码更加清晰和可维护。
-
遵循开闭原则:可以添加新的策略而不修改现有代码,符合"对扩展开放,对修改封闭"的原则。
-
组合优于继承:策略模式使用组合而非继承来实现算法的变化,更加灵活。
14.2 常见应用场景
策略模式在实际应用中非常广泛,包括但不限于:
- 排序算法:根据不同需求选择不同的排序算法
- 支付处理:处理不同的支付方式
- 验证规则:应用不同的验证规则和条件
- 数据压缩:选择不同的压缩算法
- 路由算法:选择不同的路由算法计算路径
- 文本格式化:应用不同的文本格式化规则
- 价格计算:根据不同条件计算价格和折扣
14.3 与Java 8+的结合
随着Java 8引入Lambda表达式和函数式接口,策略模式变得更加简洁和强大。你可以使用Lambda表达式创建策略,而不需要定义多个具体策略类,这使得策略模式更容易使用和维护。
例如,使用Comparator
、Predicate
、Function
等函数式接口,你可以轻松地创建和组合各种策略,而不需要编写大量的样板代码。
14.4 最终建议
- 适时使用:策略模式是一种强大的工具,但不是万能的。在引入策略模式之前,考虑是否真的需要算法的动态变化。
- 减少复杂性:避免过度使用策略模式,这可能导致系统中类的数量过多。
- 结合其他模式:策略模式通常与工厂模式、命令模式等一起使用,以提供更完整的解决方案。
通过恰当地使用策略模式,你可以创建更加灵活、可维护和可扩展的应用程序,有效地处理算法的变化,同时保持系统的整体结构简洁清晰。
相关文章:
策略模式(Strategy Pattern)详解
文章目录 1. 什么是策略模式?2. 为什么需要策略模式?3. 策略模式的核心概念3.1 策略(Strategy)3.2 具体策略(Concrete Strategy)3.3 上下文(Context) 4. 策略模式的结构5. 策略模式的…...
websheet 之 table表格
本控件只实现table的基础功能。 {.is-danger} 一、table基本使用 可以通过addTable函数动态增加table,代码如下: let tableColumn [];let col 1;tableColumn.push(测试 (col) 列);tableColumn.push(测试 (col) 列);tableColumn.push(测试 (col) …...
Python Cookbook-6.9 快速复制对象
任务 为了使用 copy.copy,需要实现特殊方法__copy__。而且你的类的__init__比较耗时所以你希望能够绕过它并获得一个“空的”未初始化的类实例。 解决方案 下面的解决方案可同时适用于新风格和经典类: def empty_copy(obj):class Empty(obj.__class__):def __in…...
Linux NIO 原理深度解析:从内核到应用的高性能 I/O 之道
Linux 的 非阻塞 I/O(Non-blocking I/O,NIO) 是构建高性能服务器的核心技术,其核心思想是通过 事件驱动模型 和 零拷贝技术 实现高并发、低延迟的网络通信。以下从底层机制到实际应用进行全面剖析。 一、Linux I/O …...
Redis 集群切片全解析:四种常见技术的原理、优劣与应用
Redis 集群切片是将数据分散存储在多个 Redis 节点上的技术,以提高系统的可扩展性和性能。以下是一些常见的 Redis 集群切片方式: 1.哈希切片 原理:通过对数据的键进行哈希运算,将哈希值映射到不同的切片(槽…...
html中margin的用法
在 HTML 页面布局中,margin 是 CSS 中用于设置 元素与元素之间的外边距(即元素外部的空白区域) 的属性。 它可以单独设置四个方向的边距:上(top)、右(right)、下(bottom…...
网络流量分析 | 流量分析基础
流量分析是网络安全领域的一个子领域,其主要重点是调查网络数据,以发现问题和异常情况。本文将涵盖网络安全和流量分析的基础知识。 网络安全与网络中的数据 网络安全的两个最关键概念就是:认证(Authentication)和授…...
语音合成之六端到端TTS模型的演进
端到端TTS模型的演进 引言Tacotron:奠基之作FastSpeech:解决效率瓶颈VITS:实现高保真和富有表现力的语音SparkTTS:利用LLM实现高效可控的TTSCosyvoice:一种可扩展的多语种TTS方法端到端TTS模型的演进与未来方向 引言 …...
文件的读取操作
#import time # 导入time 库 # 打开文件 fileopen("E:\Dasktape/python_test.txt","r",encoding"UTF-8")# 读取文件 print(f"读取文件的所有内容内容:{file.read()}\n") #\n是换行字符 print(f"读取10个字节的文件内容:{file.re…...
【Linux学习笔记】进程的fork创建 exit终止 wait等待
【Linux学习笔记】进程的fork创建 exit终止 wait等待 🔥个人主页:大白的编程日记 🔥专栏:Linux学习笔记 文章目录 【Linux学习笔记】进程的fork创建 exit终止 wait等待前言1.进程创建1.1 fork函数初识1.2fork函数返回值1.3写时拷…...
一种专用车辆智能配电模块的设计解析:技术革新与未来展望
关键词:智能配电模块、STM32、CAN总线、电子开关、新能源汽车 引言:传统配电系统的痛点与智能化转型 传统配电系统依赖继电器和保险丝,存在体积大、寿命短、智能化低等缺陷(如图1)。而新能源汽车和无人驾驶技术对配电…...
第TR5周:Transformer实战:文本分类
🍨 本文为🔗365天深度学习训练营中的学习记录博客 🍖 原作者:K同学啊 1.准备工作 1.1.加载数据 import torch import torch.nn as nn import torchvision import os,PIL,warnings import pandas as pd warnings.filterwarnings…...
Python爬虫(4)CSS核心机制:全面解析选择器分类、用法与实战应用
目录 一、背景与重要性二、CSS选择器基础与分类2.1 什么是选择器?2.2 选择器分类与语法 三、核心选择器详解与实战案例3.1 基础选择器:精准定位元素3.2 组合选择器:元素关系控制3.3 伪类与伪元素:动态与虚拟元素3…...
复杂地形越野机器人导航新突破!VERTIFORMER:数据高效多任务Transformer助力越野机器人移动导航
作者: Mohammad Nazeri 1 ^{1} 1, Anuj Pokhrel 1 ^{1} 1, Alexandyr Card 1 ^{1} 1, Aniket Datar 1 ^{1} 1, Garrett Warnell 2 , 3 ^{2,3} 2,3, Xuesu Xiao 1 ^{1} 1单位: 1 ^{1} 1乔治梅森大学计算机科学系, 2 ^{2} 2美国陆军研究实验室&…...
ROS 快速入门教程04
12.激光雷达工作原理 激光雷达的作用是探照周围障碍物的距离,按照测量维度可以分为单线雷达和多线雷达。 按照测量原理可以分为三角测距雷达和TOF雷达。按照工作方式可以分为固态雷达和机械旋转雷达。 本次讲解以TOF雷达为例,雷达发射器发射激光遇到障碍…...
Node.js 开发项目
初始化 npm init## npm install 编辑packege.json 添加,以支持ES6的语法 "type": "module" 连接mysql示例 import db from ./db/ops_mysql.jsconst createTable async () > {const insert_data CREATE TABLE IF NOT EXISTS users (…...
Linux系统下的常用网络命令
1.ping命令 作用:用来检测网络的连通情况和分析网络速度;根据域名得到服务器IP;根据ping返回的TTL值来判断对方所使用的操作系统及数据包经过路由器数量。 参数:-c 数字:设定ping命令发出的消息包数量,如无…...
【器件专题1——IGBT第1讲】IGBT:电力电子领域的 “万能开关”,如何撑起新能源时代?
一、IGBT 是什么?重新认识这个 “低调的电力心脏” 你可能没听过 IGBT,但一定用过它驱动的设备:家里的变频空调、路上的电动汽车、屋顶的光伏逆变器,甚至高铁和电网的核心部件里,都藏着这个 “电力电子开关的瑞士军刀”…...
C++23 新特性深度落地与最佳实践
一、引言 C 作为一门历史悠久且广泛应用的编程语言,一直在不断发展和演进。C23 作为 C 标准的一个重要版本,引入了许多令人期待的新特性,这些特性不仅提升了代码的可读性、可维护性,还增强了程序的性能和安全性。本文将深入探讨 …...
26考研 | 王道 | 数据结构笔记博客总结
26考研 | 王道 | 数据结构笔记博客总结 笔者博客网站 分类: 数据结构 | Darlingの妙妙屋 26考研 | 王道 | 数据结构 | 第一章 数据结构绪论 | Darlingの妙妙屋 26考研 | 王道 | 数据结构 | 第二章 线性表 | Darlingの妙妙屋 26考研 | 王道 | 数据结构 | 第三章 栈和队列 |…...
Bolsig+超详细使用教程
文章目录 Bolsig介绍Bolsig的使用 Bolsig介绍 BOLSIG 是一款用于求解弱电离气体中电子玻尔兹曼方程的免费计算程序,适用于均匀电场条件下的群体实验、气体放电及碰撞型低温等离子体研究。在此类环境中,电子分布函数呈现非麦克斯韦特性,其形态…...
基于线性LDA算法对鸢尾花数据集进行分类
基于线性LDA算法对鸢尾花数据集进行分类 1、效果 2、流程 1、加载数据集 2、划分训练集、测试集 3、创建模型 4、训练模型 5、使用LDA算法 6、画图3、示例代码 # 基于线性LDA算法对鸢尾花数据集进行分类# 基于线性LDA算法对鸢尾花数据集进行分类 import numpy as np import …...
C#高级语法--接口
先引用一些通俗一点的话语说明 1. 接口就像“插座标准”(解耦) 🧩 场景: 你家的手机充电器(USB-C、Lightning)必须插进匹配的插座才能充电。问题:如果每个手机品牌插座都不一样,你换手机就得换充电器,太麻烦了!💡 接口的作用: 定义一个通用的充电口标准(比如U…...
软测面经(私)
测试流程 分析需求——>制定测试计划——>设计测试用例——>执行测试——>编写测试报告 黑盒测试 等价类划分、边界值分析法、猜错法、随机数法、因果图。 白盒测试 代码检查法、程序变异、静态结构分析法、静态质量度量法、符号测试法、逻辑覆盖法、域测试、…...
线程函数库
pthread_create函数 pthread_create 是 POSIX 线程库(pthread)中的一个函数,用于创建一个新的线程。 头文件 #include <pthread.h> 函数原型 int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*s…...
数据结构初阶:排序
概述:本篇博客主要介绍关于排序的算法。 目录 1.排序概念及应用 1.1 概念 1.2 运用 1.3 常见的排序算法 2. 实现常见排序算法 2.1 插入排序 2.1.1 直接插入排序 2.1.2 希尔排序 2.2 选择排序 2.2.1 直接选择排序 2.2.2 堆排序 2.3 交换排序 2.3.1 冒泡排序…...
openwrt查询网关的命令
方法一:route -n 方法二:ip route show...
优化非线性复杂系统的参数
非线性项组合的系统 对于系统中的每一个复杂拟合,即每一个残差函数,都能表示为非线性方程的趋势,例如较为复杂的系统函数组, from optimtool.base import sp, np x sp.symbols("x1:5") res1 0.5*x[0] 0.2*x[1] 1.…...
【QQMusic项目界面开发复习笔记】第二章
🌹 作者: 云小逸 🤟 个人主页: 云小逸的主页 🤟 motto: 要敢于一个人默默的面对自己,强大自己才是核心。不要等到什么都没有了,才下定决心去做。种一颗树,最好的时间是十年前,其次就是现在&…...
并发编程【深度解剖】
并发介绍 谈到并发,随之而来的就是那几个问题。并发 并行 线程 进程 注意!!!本篇文章更多用诙谐的语调讲解,为保证易于理解,不够官方正式,所以可以结合AI读本篇文章,并且本文是以 g…...
前端如何连接tcp 服务,接收数据
在传统的浏览器前端环境中,由于浏览器的同源策略和安全限制,无法直接建立 TCP 连接。不过,可以通过 WebSocket 或者使用 WebRTC 来间接实现与 TCP 服务的通信,另外在 Node.js 环境中可以直接使用 net 模块建立 TCP 连接。下面分别…...
用C语言实现——一个中缀表达式的计算器。支持用户输入和动画演示过程。
一、思路概要和知识回顾 1.思路概要 ①中缀表达式计算: 需要处理运算符的优先级,可能需要用到栈结构。 ❗❗如何将中缀表达式转换为后缀表达式?或者直接计算? 通常,中缀转后缀(逆波兰式)再…...
使用 Pandas 进行多格式数据整合:从 Excel、JSON 到 HTML 的处理实战
前言 在数据处理与分析的实际场景中,我们经常需要整合不同格式的数据,例如 Excel 表格、JSON 配置文件、HTML 报表等。本文以一个具体任务(蓝桥杯模拟练习题)为例,详细讲解如何使用 Python 的 Pandas 库结合其他工具&…...
常见游戏引擎介绍与对比
Unreal Engine (UE4/UE5) 主语言:C Unreal Engine 主要使用 C 作为开发语言。C 提供了高性能的底层控制,适用于需要精细调优的 AAA 级游戏。C 在 Unreal 中用于开发核心游戏逻辑、物理引擎等性能要求较高的部分。 脚本语言:蓝图(B…...
第十一天 主菜单/设置界面 过场动画(Timeline) 成就系统(Steam/本地) 多语言支持
前言 对于刚接触Unity的新手开发者来说,构建完整的游戏系统往往充满挑战。本文将手把手教你实现游戏开发中最常见的四大核心系统:主菜单界面、过场动画、成就系统和多语言支持。每个模块都将结合完整代码示例,使用Unity 2022 LTS版本进行演示…...
vue3 使用 vite 管理多个项目,实现各子项目独立运行,独立打包
场景: 之前写过一篇 vite vue2 的配置,但是现在项目使用 vue3 较多,再更新一下 vue脚手架初始化之后的项目,每个项目都是独立的,导致项目多了之后,node依赖包过多,占用内存较多。想实现的效果…...
k8s(9) — zookeeper集群部署(亲和性、污点与容忍测试)
一、部署思路 1、前期设想 zookeeper集群至少需要运行3个pod集群才能够正常运行,考虑到节点会有故障的风险这个3个pod最好分别运行在3个不同的节点上(为了实现这一需要用到亲和性和反亲和性概念),在部署的时候对zookeeper运行的pod打标签加…...
Linux操作系统复习
Linux操作系统复习 一. Linux的权限和shell原理1. Linux从广义上讲是什么 从狭义上讲是什么?2. shell是什么?3. 为什么要设置一个shell外壳而不是直接和linux 内核沟通4. shell的原理是什么5. Linux中权限的概念6. 如何提升当前操作的权限7. 文件访问者的…...
深入解析 Linux 中动静态库的加载机制:从原理到实践
引言 在 Linux 开发中,动静态库是代码复用的核心工具。静态库(.a)和动态库(.so)的加载方式差异显著,直接影响程序的性能、灵活性和维护性。本文将深入剖析两者的加载机制,结合实例演示和底层原…...
总账主数据——Part 2 科目-1
本文主要介绍在S4 HANA OP中 总账主数据的后台配置及前台操作。 目录 1. 准备 1.1 科目表的定义(OB13) 1.2 给公司代码分配科目表(OB62) 1.3 定义科目组(OBD4) 1.4 定义留存收益科目(OB53) 1.5 维护科目表层“文本标识” (OBT6) 1.6 维护公司代码层“文本标识” (OBT…...
借助内核逻辑锁pagecache到内存
一、背景 内存管理是一个永恒的主题,尤其在内存紧张触发内存回收的时候。系统在通过磁盘获取磁盘上的文件的内容时,若不开启O_DIRECT方式进行读写,磁盘上的任何东西都会被缓存到系统里,我们称之为page cache。可以想象࿰…...
✨ Apifox:这玩意儿是接口界的“瑞士军刀”吧![特殊字符][特殊字符]
——全网最皮最全测评,打工人看了直呼“真香” 📢 友情提醒 还在用 Postman 测接口、Swagger 写文档、Mock.js 造假数据、脑细胞搞团队协作? 停! 你仿佛在玩《工具人环游记》,而隔壁同事已经用 Apifox 「一杆清台」了…...
《普通逻辑》学习记录——性质命题及其推理
目录 一、性质命题概述 二、性质命题的种类 2.1、性质命题按质的分类 2.2、性质命题按量的分类 2.3、性质命题按质和量结合的分类 2.4、性质命题的基本形式归纳 三、四种命题的真假关系 3.1、性质命题与对象关系 3.2、四种命题的真假判定 3.3、四种命题的对当关系 四、四种命题…...
设备接入与APP(应用程序)接入华为云iotDA平台的路径元素有哪些不同?
目录 壹、设备接入华为云iotDA 🏢 形象比喻:设备 员工,IoTDA 平台 安保森严的总部大楼 一、📍 平台接入地址 总部大楼地址 二、🧾 接入凭证 出入证 / 门禁卡 / 工牌 1. 设备密钥或证书 2. 预置接入凭证密钥&a…...
【git#4】分支管理 -- 知识补充
一、bug 分支 假如我们现在正在 dev2 分支上进行开发,开发到一半,突然发现 master 分支上面有 bug,需要解决。 在Git中,每个 bug 都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临…...
AXOP34062: 40V双通道运算放大器
AXOP34062是一款通用型高压双通道运算放大器,产品的工作电压为2.5V至40V,具有25MHz的带宽,压摆率为10V/μs,静态电流为650A。较高的耐压和带宽使其可以胜任绝大多数的高压应用场景。 主要特性 轨到轨的输入输出范围低输入失调电…...
OpenCv高阶(十)——光流估计
文章目录 前言一、光流估计二、使用步骤1、导库读取视频、随机初始化颜色2、初始化光流跟踪3、视频帧处理循环4、光流计算与可视化5、循环控制与资源释放完整代码 总结 前言 在计算机视觉领域,光流估计是捕捉图像序列中像素点运动信息的核心技术。它描述了图像中每…...
BS客户端的单点登录
1、参数类似于“XXXXX://?userIdsystem&time1696830378038&token38a8ea526537766f01ded33a6cdfa5bd” 2、在config里加一个LoginSecret参数可随意指定一个字符串 3、BS登录代码里会对“LoginSecret的参数值用户ID时间戳”进行MD5加密形成token,与传过来的…...
通讯录完善版本(详细讲解+源码)
目录 前言 一、使通讯可以动态更新内存 1、contact.h 2、contact.c 存信息: 删除联系人,并试一个不存在的人的信息,看看会不会把其他人删了 编辑 修改: 编辑 排序: 编辑 销毁: 编辑 …...
第3讲:ggplot2完美入门与美化细节打磨——从基础绘制到专业级润色
目录 1. 为什么选择ggplot2? 2. 快速了解ggplot2绘图核心逻辑 3. 基础绘图示范:柱状图、折线图、散点图 (1)简单柱状图 (2)折线图示范 (3)高级散点图 + 拟合线 4. 精细美化:细节打磨决定专业感 5. 推荐的美化小插件(可选进阶) 6. 小练习:快速上手一幅美化…...