策略设计模式
策略设计模式是一种行为设计模式,它允许您定义一系列算法,将每个算法放入一个单独的类中,并使其对象可互换——允许算法独立于使用它的客户端而变化。
它在以下情况下特别有用:
您可以通过多种方式执行任务或计算。
类的行为需要在运行时动态更改。
你希望避免为每个变体使用条件逻辑(like 或 statement)使代码变得混乱。if-elseswitch
当您有多种方法来实现同一目标时,您可以在类中使用分支逻辑来处理不同的情况。例如,a 可能用于 在信用卡、现金或 UPI 之间进行选择。PaymentServiceif-else
但是,随着支付类型的添加,这种方法变得难以扩展,违反了开放/封闭原则,并使您的代码更难测试和维护。
策略模式通过将每个行为封装在自己的类中并在运行时将责任委托给正确的策略来解决这个问题——保持核心逻辑的干净、可扩展和可测试。
让我们通过一个真实世界的示例,看看如何应用策略模式来构建更灵活、更可维护的工作流程来处理不同的行为。
问题:运费计算
想象一下,您正在 为电子商务平台构建一个运费计算器。
与大多数实际应用程序一样,运费可能会根据不同的业务规则或外部提供商而有所不同。
以下是您可能需要支持的一些常见策略:
统一费率:固定费用(例如,每批货物 10 美元),无论重量或目的地如何。
基于重量:成本按每公斤固定金额计算。
基于距离:根据目的地区域的不同,费率不同(例如,A 区 = 5 美元,B 区 = 12 美元)。
第三方 API:从 FedEx 或 UPS 等提供商获取动态费率。
一个快速而朴素的解决方案可能是使用一长串条件在单个类中实现所有这些逻辑:
public class ShippingCostCalculatorNaive {public double calculateShippingCost(Order order, String strategyType) {double cost = 0.0;if ("FLAT_RATE".equalsIgnoreCase(strategyType)) {System.out.println("Calculating with Flat Rate strategy.");cost = 10.0; // Fixed $10} else if ("WEIGHT_BASED".equalsIgnoreCase(strategyType)) {System.out.println("Calculating with Weight-Based strategy.");cost = order.getTotalWeight() * 2.5; // $2.5 per kg} else if ("DISTANCE_BASED".equalsIgnoreCase(strategyType)) {System.out.println("Calculating with Distance-Based strategy.");if ("ZoneA".equals(order.getDestinationZone())) {cost = 5.0;} else if ("ZoneB".equals(order.getDestinationZone())) {cost = 12.0;} else {cost = 20.0; // Default for other zones}} else if ("THIRD_PARTY_API".equalsIgnoreCase(strategyType)) {System.out.println("Calculating with Third-Party API strategy.");// Simulate API callcost = 7.5 + (order.getOrderValue() * 0.02); // Example: base fee + % of order value} else {throw new IllegalArgumentException("Unknown shipping strategy type: " + strategyType);}System.out.println("Calculated Shipping Cost: $" + cost);return cost;}
}
使用它的客户端代码
public class ECommerceAppV1 {public static void main(String[] args) {ShippingCostCalculatorNaive calculator = new ShippingCostCalculatorNaive();Order order1 = new Order();System.out.println("--- Order 1 ---");calculator.calculateShippingCost(order1, "FLAT_RATE");calculator.calculateShippingCost(order1, "WEIGHT_BASED");calculator.calculateShippingCost(order1, "DISTANCE_BASED");calculator.calculateShippingCost(order1, "THIRD_PARTY_API");// What if we want to try a new "PremiumZone" strategy?// We have to modify the ShippingCostCalculatorNaive class!}
}
这种方法有什么问题?
虽然最初看起来不错,但随着系统的发展,这种设计很快就会变得脆弱和有问题:
违反开/闭原则
每次添加新的配送策略(例如“Prime 会员免费配送”或“碳抵消的生态配送”)时,您都必须修改该 类别。这使得类可以修改,而不是因更改而关闭并对扩展开放。ShippingCostCalculatorNaive
臃肿的条件逻辑
随着更多策略的引入,链条变得越来越大且不可读。它使您的代码变得混乱,并使调试变得更加困难。if-else
难以孤立测试
每种策略都纠缠在一种方法中,因此更难独立测试个人行为。您必须设置整个 对象并手动选择策略类型,以便测试一个案例。Order
代码重复的风险
如果不同的服务(例如结账、退货、物流)使用相似的逻辑,您最终可能会在多个地方复制这些条件块。
低内聚力
计算器类做得太多了。它知道如何处理运输成本的每一种可能的算法,而不是专注于编排计算。
我们需要一种更干净的方法,允许:
每个运输策略都要独立定义
运行时轻松的即插即用行为
开放式扩展,封闭式设计
这就是策略设计模式发挥作用的地方。
策略设计模式
策略模式定义了一系列算法,封装了每个算法,并使它们可以互换。
简单来说:不是使用 or 语句对逻辑进行硬编码 ,而是将行为委托给策略对象。if-elseswitch
该模式允许客户端(如运费计算器或支付处理器)在运行时插入特定行为,而无需更改系统的底层逻辑。
类图
它的工作原理如下:
策略接口(例如ShippingStrategy)
声明算法方法的通用接口。所有具体策略都实现了这个接口。
具体策略(例如,FlatRateShipping、WeightBasedShipping)
这些是接口的单独实现。 每个类都封装了不同的算法。Strategy
上下文类(例如 ShippingCostService)
这是使用策略来执行任务的主要类。它保存对对象的引用 并将计算委托给它。上下文不知道也不关心正在使用哪种特定策略,它只知道它有一个可以计算运输成本的策略。Strategy
客户端代码
客户负责:
创建适当的具体策略实例
将其传递给Context
如果需要,更改运行时的策略 Context
实施策略模式
让我们重构我们的运费计算器。
- 1. 定义策略接口 (ShippingStrategy)
我们首先为所有运输策略定义一个通用接口。这允许上下文类 () 可以互换地与任何策略交互。ShippingCostService
public interface ShippingStrategy {double calculateCost(Order order);
}
每个具体策略都会实现这个接口,并提供自己的算法来计算运费。
- 2. 实施具体策略类
每个运输策略将其逻辑封装在一个专用类中。这使得行为模块化并且易于扩展或测试。
统一运费
public class FlatRateShipping implements ShippingStrategy {private double rate;public FlatRateShipping(double rate) {this.rate = rate;}@Overridepublic double calculateCost(Order order) {System.out.println("Calculating with Flat Rate strategy ($" + rate + ")");return rate;}
}
基于重量的运输
public class WeightBasedShipping implements ShippingStrategy {private final double ratePerKg;public WeightBasedShipping(double ratePerKg) {this.ratePerKg = ratePerKg;}@Overridepublic double calculateCost(Order order) {System.out.println("Calculating with Weight-Based strategy ($" + ratePerKg + "/kg)");return order.getTotalWeight() * ratePerKg;}
}
基于距离的运输
public class DistanceBasedShipping implements ShippingStrategy {private double ratePerKm;public DistanceBasedShipping(double ratePerKm) {this.ratePerKm = ratePerKm;}@Overridepublic double calculateCost(Order order) {System.out.println("Calculating with Distance-Based strategy for zone: " + order.getDestinationZone());return switch (order.getDestinationZone()) {case "ZoneA" -> ratePerKm * 5.0;case "ZoneB" -> ratePerKm * 7.0;default -> ratePerKm * 10.0;}; }
}
第三方ApiShipping
public class ThirdPartyApiShipping implements ShippingStrategy {private final double baseFee;private final double percentageFee;public ThirdPartyApiShipping(double baseFee, double percentageFee) {this.baseFee = baseFee;this.percentageFee = percentageFee;}@Overridepublic double calculateCost(Order order) {System.out.println("Calculating with Third-Party API strategy.");// Simulate API callreturn baseFee + (order.getOrderValue() * percentageFee);}
}
- 3. 创建上下文类 (ShippingCostService)
上下文类维护对 a 的引用 并将计算委托给它。ShippingStrategy
public class ShippingCostService {private ShippingStrategy strategy;// Constructor to set initial strategypublic ShippingCostService(ShippingStrategy strategy) {this.strategy = strategy;}// Method to change strategy at runtimepublic void setStrategy(ShippingStrategy strategy) {System.out.println("ShippingCostService: Strategy changed to " + strategy.getClass().getSimpleName());this.strategy = strategy;}public double calculateShippingCost(Order order) {if (strategy == null) {throw new IllegalStateException("Shipping strategy not set.");}double cost = strategy.calculateCost(order); // Delegate to the strategySystem.out.println("ShippingCostService: Final Calculated Shipping Cost: $" + cost +" (using " + strategy.getClass().getSimpleName() + ")");return cost;}
}
这个类完全与它使用的算法无关,它只是委托给当前设置的任何策略。
- 4. 客户端代码
让我们看看客户端如何在运行时在不同策略之间动态切换:
public class ECommerceAppV2 {public static void main(String[] args) {Order order1 = new Order();// Create different strategy instancesShippingStrategy flatRate = new FlatRateShipping(10.0);ShippingStrategy weightBased = new WeightBasedShipping(2.5);ShippingStrategy distanceBased = new DistanceBasedShipping(5.0);ShippingStrategy thirdParty = new ThirdPartyApiShipping(7.5, 0.02);// Create context with an initial strategyShippingCostService shippingService = new ShippingCostService(flatRate);System.out.println("--- Order 1: Using Flat Rate (initial) ---");shippingService.calculateShippingCost(order1);System.out.println("\n--- Order 1: Changing to Weight-Based ---");shippingService.setStrategy(weightBased);shippingService.calculateShippingCost(order1);System.out.println("\n--- Order 1: Changing to Distance-Based ---");shippingService.setStrategy(distanceBased);shippingService.calculateShippingCost(order1);System.out.println("\n--- Order 1: Changing to Third-Party API ---");shippingService.setStrategy(thirdParty);shippingService.calculateShippingCost(order1);// Adding a NEW strategy is easy:// 1. Create a new class implementing ShippingStrategy (e.g., FreeShippingStrategy)// 2. Client can then instantiate and use it:// ShippingStrategy freeShipping = new FreeShippingStrategy();// shippingService.setStrategy(freeShipping);// shippingService.calculateShippingCost(primeMemberOrder);// No modification to ShippingCostService is needed!}
}
注意这是多么干净!
内部没有条件逻辑。ShippingCostService
策略是封装的、可重用的且易于测试。
添加新策略只需要创建一个新类,无需更改服务或现有逻辑。
您可以在运行时切换策略,而不会破坏任何现有功能。
这就是策略模式的强大之处:它将你的工作与你的工作方式分开,从而产生干净、可扩展和可维护的代码。