设计模式-策略模式详解
-
- [1. 策略模式是什么?](#1. 策略模式是什么?)
- [2. 为什么需要策略模式?](#2. 为什么需要策略模式?)
- [3. 策略模式的结构](#3. 策略模式的结构)
- [4. 策略模式的Java实现](#4. 策略模式的Java实现)
- [5. 策略模式的优势](#5. 策略模式的优势)
-
- [5.1 对比原始实现](#5.1 对比原始实现)
- [5.2 核心优势](#5.2 核心优势)
- [6. 策略模式的实际应用场景](#6. 策略模式的实际应用场景)
- [7. 策略模式的变体与最佳实践](#7. 策略模式的变体与最佳实践)
-
- [7.1 使用枚举简化策略选择](#7.1 使用枚举简化策略选择)
- [7.2 策略工厂模式](#7.2 策略工厂模式)
- [7.3 Lambda表达式简化策略模式(Java 8+)](#7.3 Lambda表达式简化策略模式(Java 8+))
- [8. 策略模式的局限性](#8. 策略模式的局限性)
- [9. 策略模式与其他模式的关系](#9. 策略模式与其他模式的关系)
- [10. 总结](#10. 总结)
在软件开发中,常常会遇到这样的情况:一个功能有多种实现方式,比如支付功能可以有支付宝、微信支付、银行卡支付等多种方式;排序功能可以有快速排序、归并排序、堆排序等多种算法。如果将这些实现都硬编码在一个类中,代码会变得臃肿、难以维护,而且新增一种实现方式时需要修改原有代码。存在一种优雅解决此类问题的设计模式------策略模式。
1. 策略模式是什么?
策略模式是一种行为设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。
简单来说,策略模式的核心思想是:
- 定义算法家族:将相关的算法封装成独立的策略类
- 让算法可互换:这些策略类实现相同的接口,可以相互替换
- 分离关注点:将算法的使用与算法的实现分离
2. 为什么需要策略模式?
让我们先看一个不使用策略模式的"反面教材":
java
// 反面教材:使用大量if-else判断的代码
public class PaymentProcessor {
public void processPayment(String paymentType, double amount) {
if ("ALIPAY".equals(paymentType)) {
System.out.println("使用支付宝支付:" + amount + "元");
// 支付宝支付的具体逻辑...
} else if ("WECHAT_PAY".equals(paymentType)) {
System.out.println("使用微信支付:" + amount + "元");
// 微信支付的具体逻辑...
} else if ("CREDIT_CARD".equals(paymentType)) {
System.out.println("使用信用卡支付:" + amount + "元");
// 信用卡支付的具体逻辑...
} else if ("PAYPAL".equals(paymentType)) {
System.out.println("使用PayPal支付:" + amount + "元");
// PayPal支付的具体逻辑...
} else {
throw new IllegalArgumentException("不支持的支付方式");
}
}
}
这段代码的问题很明显:
- 违反开闭原则:新增支付方式需要修改原有代码
- 代码臃肿:所有支付逻辑都挤在一个方法中
- 难以测试:每个支付逻辑无法独立测试
- 职责不清:一个类负责了太多支付方式
3. 策略模式的结构
策略模式包含三个核心角色:
使用
实现
实现
<<interface>>
Strategy
+execute(data) : void
ConcreteStrategyA
+execute(data) : void
ConcreteStrategyB
+execute(data) : void
Context
-strategy: Strategy
+setStrategy(Strategy strategy) : void
+executeStrategy(data) : void
- 策略接口:定义所有支持的算法的公共接口
- 具体策略类:实现策略接口的具体算法
- 上下文类:持有一个策略对象的引用,通过它来调用具体策略
4. 策略模式的Java实现
让我们用策略模式重构上面的支付示例:
步骤1:定义策略接口
java
/**
* 支付策略接口
* 定义所有支付方式必须实现的方法
*/
public interface PaymentStrategy {
/**
* 支付方法
* @param amount 支付金额
*/
void pay(double amount);
}
步骤2:实现具体策略类
java
/**
* 支付宝支付策略
*/
public class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用支付宝支付:" + amount + "元");
// 实际的支付宝支付逻辑...
System.out.println("调用支付宝API...");
System.out.println("支付成功!");
}
}
/**
* 微信支付策略
*/
public class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用微信支付:" + amount + "元");
// 实际的微信支付逻辑...
System.out.println("调用微信支付API...");
System.out.println("支付成功!");
}
}
/**
* 信用卡支付策略
*/
public class CreditCardStrategy implements PaymentStrategy {
private String cardNumber;
private String expiryDate;
private String cvv;
public CreditCardStrategy(String cardNumber, String expiryDate, String cvv) {
this.cardNumber = cardNumber;
this.expiryDate = expiryDate;
this.cvv = cvv;
}
@Override
public void pay(double amount) {
System.out.println("使用信用卡支付:" + amount + "元");
System.out.println("卡号:" + maskCardNumber(cardNumber));
// 实际的信用卡支付逻辑...
System.out.println("验证信用卡信息...");
System.out.println("支付成功!");
}
private String maskCardNumber(String cardNumber) {
if (cardNumber.length() <= 4) {
return cardNumber;
}
return "****-****-****-" + cardNumber.substring(cardNumber.length() - 4);
}
}
步骤3:创建上下文类
java
/**
* 支付上下文类
* 负责管理支付策略
*/
public class PaymentContext {
// 持有一个支付策略的引用
private PaymentStrategy paymentStrategy;
/**
* 设置支付策略
* @param paymentStrategy 支付策略
*/
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
/**
* 执行支付
* @param amount 支付金额
*/
public void executePayment(double amount) {
if (paymentStrategy == null) {
throw new IllegalStateException("请先设置支付策略");
}
paymentStrategy.pay(amount);
}
}
步骤4:客户端使用
java
public class Client {
public static void main(String[] args) {
// 创建支付上下文
PaymentContext paymentContext = new PaymentContext();
// 使用支付宝支付
System.out.println("=== 使用支付宝支付 ===");
paymentContext.setPaymentStrategy(new AlipayStrategy());
paymentContext.executePayment(100.0);
// 使用微信支付
System.out.println("\n=== 使用微信支付 ===");
paymentContext.setPaymentStrategy(new WechatPayStrategy());
paymentContext.executePayment(200.0);
// 使用信用卡支付
System.out.println("\n=== 使用信用卡支付 ===");
paymentContext.setPaymentStrategy(new CreditCardStrategy(
"1234-5678-9012-3456", "12/25", "123"));
paymentContext.executePayment(300.0);
}
}
输出结果:
=== 使用支付宝支付 ===
使用支付宝支付:100.0元
调用支付宝API...
支付成功!
=== 使用微信支付 ===
使用微信支付:200.0元
调用微信支付API...
支付成功!
=== 使用信用卡支付 ===
使用信用卡支付:300.0元
卡号:****-****-****-3456
验证信用卡信息...
支付成功!
5. 策略模式的优势
5.1 对比原始实现
让我们对比一下使用策略模式前后的代码结构:
| 对比维度 | 原始实现(if-else) | 策略模式实现 |
|---|---|---|
| 新增支付方式 | 需要修改PaymentProcessor类 | 只需新增一个策略类 |
| 代码结构 | 一个方法包含所有逻辑 | 逻辑分散到各个策略类 |
| 可测试性 | 难以单独测试每种支付方式 | 每个策略可独立测试 |
| 遵循原则 | 违反开闭原则 | 符合开闭原则 |
5.2 核心优势
- 开闭原则:新增策略无需修改现有代码
- 消除条件判断:用多态代替复杂的if-else或switch-case
- 提高可复用性:策略可以在不同的上下文中复用
- 简化单元测试:每个策略可以独立测试
- 运行时切换:可以在运行时动态切换策略
6. 策略模式的实际应用场景
场景1:排序算法选择
java
// 策略接口
interface SortStrategy {
void sort(int[] array);
}
// 具体策略
class BubbleSortStrategy implements SortStrategy {
@Override
public void sort(int[] array) {
System.out.println("使用冒泡排序");
// 冒泡排序实现...
}
}
class QuickSortStrategy implements SortStrategy {
@Override
public void sort(int[] array) {
System.out.println("使用快速排序");
// 快速排序实现...
}
}
// 上下文
class SortContext {
private SortStrategy strategy;
public void setStrategy(SortStrategy strategy) {
this.strategy = strategy;
}
public void sortArray(int[] array) {
strategy.sort(array);
}
}
场景2:折扣计算
java
// 策略接口
interface DiscountStrategy {
double calculate(double price);
}
// 具体策略
class NoDiscountStrategy implements DiscountStrategy {
@Override
public double calculate(double price) {
return price;
}
}
class PercentageDiscountStrategy implements DiscountStrategy {
private double percentage;
public PercentageDiscountStrategy(double percentage) {
this.percentage = percentage;
}
@Override
public double calculate(double price) {
return price * (1 - percentage);
}
}
class FixedAmountDiscountStrategy implements DiscountStrategy {
private double amount;
public FixedAmountDiscountStrategy(double amount) {
this.amount = amount;
}
@Override
public double calculate(double price) {
return Math.max(0, price - amount);
}
}
// 上下文
class ShoppingCart {
private DiscountStrategy discountStrategy;
public void setDiscountStrategy(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public double calculateTotal(double price) {
return discountStrategy.calculate(price);
}
}
7. 策略模式的变体与最佳实践
7.1 使用枚举简化策略选择
java
/**
* 使用枚举管理策略
*/
public enum PaymentType {
ALIPAY(new AlipayStrategy()),
WECHAT_PAY(new WechatPayStrategy()),
CREDIT_CARD(new CreditCardStrategy("default", "01/30", "000"));
private final PaymentStrategy strategy;
PaymentType(PaymentStrategy strategy) {
this.strategy = strategy;
}
public PaymentStrategy getStrategy() {
return strategy;
}
}
// 使用方式
public class PaymentService {
public void processPayment(PaymentType paymentType, double amount) {
PaymentStrategy strategy = paymentType.getStrategy();
strategy.pay(amount);
}
}
7.2 策略工厂模式
java
/**
* 策略工厂类
*/
public class PaymentStrategyFactory {
private static final Map<String, PaymentStrategy> strategies = new HashMap<>();
static {
strategies.put("ALIPAY", new AlipayStrategy());
strategies.put("WECHAT_PAY", new WechatPayStrategy());
strategies.put("CREDIT_CARD", new CreditCardStrategy("default", "01/30", "000"));
}
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);
}
}
// 使用方式
public class PaymentProcessor {
public void process(String paymentType, double amount) {
PaymentStrategy strategy = PaymentStrategyFactory.getStrategy(paymentType);
strategy.pay(amount);
}
}
7.3 Lambda表达式简化策略模式(Java 8+)
java
// 使用函数式接口和Lambda表达式
@FunctionalInterface
interface CalculationStrategy {
int calculate(int a, int b);
}
public class Calculator {
private CalculationStrategy strategy;
public void setStrategy(CalculationStrategy strategy) {
this.strategy = strategy;
}
public int execute(int a, int b) {
return strategy.calculate(a, b);
}
}
// 使用Lambda表达式
public class Client {
public static void main(String[] args) {
Calculator calculator = new Calculator();
// 加法策略
calculator.setStrategy((a, b) -> a + b);
System.out.println("10 + 5 = " + calculator.execute(10, 5));
// 减法策略
calculator.setStrategy((a, b) -> a - b);
System.out.println("10 - 5 = " + calculator.execute(10, 5));
// 乘法策略
calculator.setStrategy((a, b) -> a * b);
System.out.println("10 * 5 = " + calculator.execute(10, 5));
}
}
8. 策略模式的局限性
虽然策略模式有很多优点,但也存在一些局限性:
- 客户端必须了解所有策略:客户端需要知道有哪些策略可供选择
- 策略类数量可能爆炸:如果策略很多,会产生大量的小类
- 策略对象可能无状态:如果策略需要维护状态,会增加复杂性
- 增加系统复杂度:对于简单的情况,可能过度设计
9. 策略模式与其他模式的关系
- 策略模式 vs. 状态模式:两者结构相似,但意图不同。状态模式中,状态改变行为;策略模式中,客户端选择行为。
- 策略模式 vs. 工厂模式:工厂模式创建对象,策略模式使用对象。两者经常结合使用。
- 策略模式 vs. 模板方法模式:模板方法模式定义算法骨架,子类实现部分步骤;策略模式定义算法接口,具体类实现完整算法。
10. 总结
策略模式是一种简单但强大的设计模式,它通过将算法封装成独立的策略类,实现了算法的定义与使用的分离。这种分离带来了诸多好处:
- 提高了代码的可维护性:每种算法独立成类,易于理解和修改
- 增强了系统的灵活性:可以在运行时动态切换算法
- 促进了代码的复用:策略可以在不同的上下文中使用
- 简化了单元测试:每个策略可以独立测试
何时使用策略模式?
- 当一个类有多种行为,且这些行为在运行时需要动态切换时
- 当你想消除大量的条件判断语句时
- 当不同的算法需要独立变化和复用时
策略模式是每个Java开发者都应该掌握的基本设计模式之一。它不仅能帮你写出更整洁的代码,还能让你的设计更加灵活和可扩展。