设计模式-策略模式


深入理解策略模式:灵活切换算法的行为型设计模式

引言

在软件开发中,我们经常会遇到需要根据不同情况执行不同算法的场景。例如,一个电商系统需要支持多种支付方式(支付宝、微信、银行卡),一个排序功能需要支持多种排序算法(快速排序、归并排序、冒泡排序)。如果使用大量的 if-else 或 switch 语句来处理,代码会变得臃肿且难以维护。

这时,**策略模式(Strategy Pattern)**就派上用场了。策略模式是行为型设计模式之一,它将一组算法封装成独立的类,使它们可以相互替换,从而让算法的变化不会影响到使用算法的客户端。

什么是策略模式

策略模式的核心思想非常简单:定义一系列算法,将每个算法封装到独立的类中,并使它们可以相互替换

更通俗地说,策略模式就像是一个”工具箱”,里面放着不同的工具(策略)。当你需要完成某项任务时,可以根据具体情况选择最合适的工具来使用,而不需要关心工具内部是如何工作的。

模式结构

策略模式主要包含三种角色:

  1. 策略接口(Strategy Interface):定义所有策略的公共接口,客户端使用这个接口来调用具体策略
  2. 具体策略(Concrete Strategy):实现策略接口的具体算法实现类
  3. 上下文(Context):持有一个策略的引用,负责调用策略的方法

实战示例:支付系统

让我们通过一个支付系统的例子来深入理解策略模式。

场景描述

假设我们需要开发一个支付系统,支持多种支付方式:支付宝、微信支付、银行卡支付。每种支付方式的实现逻辑不同,但都需要执行支付操作。

代码实现

1. 定义策略接口

/**
 * 支付策略接口
 */
public interface PaymentStrategy {
    /**
     * 执行支付
     * @param amount 支付金额
     * @return 支付结果
     */
    boolean pay(double amount);
    
    /**
     * 获取支付方式名称
     * @return 支付方式名称
     */
    String getPaymentName();
}

2. 实现具体策略类

支付宝支付策略:

public class AlipayStrategy implements PaymentStrategy {
    
    @Override
    public boolean pay(double amount) {
        System.out.println("使用支付宝支付:" + amount + " 元");
        System.out.println("正在调用支付宝API...");
        System.out.println("支付宝支付成功!");
        return true;
    }
    
    @Override
    public String getPaymentName() {
        return "支付宝";
    }
}

微信支付策略:

public class WechatPayStrategy implements PaymentStrategy {
    
    @Override
    public boolean pay(double amount) {
        System.out.println("使用微信支付:" + amount + " 元");
        System.out.println("正在调用微信支付API...");
        System.out.println("微信支付成功!");
        return true;
    }
    
    @Override
    public String getPaymentName() {
        return "微信支付";
    }
}

银行卡支付策略:

public class BankCardStrategy implements PaymentStrategy {
    
    private String cardNumber;
    private String password;
    
    public BankCardStrategy(String cardNumber, String password) {
        this.cardNumber = cardNumber;
        this.password = password;
    }
    
    @Override
    public boolean pay(double amount) {
        System.out.println("使用银行卡支付:" + amount + " 元");
        System.out.println("卡号:" + maskCardNumber(cardNumber));
        System.out.println("正在验证银行卡信息...");
        System.out.println("银行卡支付成功!");
        return true;
    }
    
    @Override
    public String getPaymentName() {
        return "银行卡";
    }
    
    private String maskCardNumber(String cardNumber) {
        if (cardNumber == null || cardNumber.length() < 8) {
            return cardNumber;
        }
        return cardNumber.substring(0, 4) + "****" + cardNumber.substring(cardNumber.length() - 4);
    }
}

3. 创建上下文类

public class PaymentContext {
    
    private PaymentStrategy paymentStrategy;
    
    public PaymentContext(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }
    
    /**
     * 设置支付策略
     */
    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }
    
    /**
     * 执行支付
     */
    public boolean executePayment(double amount) {
        if (paymentStrategy == null) {
            throw new IllegalStateException("未设置支付策略");
        }
        System.out.println("======== 支付开始 ========");
        System.out.println("支付方式:" + paymentStrategy.getPaymentName());
        boolean result = paymentStrategy.pay(amount);
        System.out.println("======== 支付结束 ========");
        return result;
    }
}

4. 客户端调用

public class PaymentDemo {
    public static void main(String[] args) {
        // 创建支付上下文
        PaymentContext context = new PaymentContext(new AlipayStrategy());
        context.executePayment(100.0);
        
        System.out.println("\n");
        
        // 切换支付方式
        context.setPaymentStrategy(new WechatPayStrategy());
        context.executePayment(200.0);
        
        System.out.println("\n");
        
        // 使用银行卡支付
        context.setPaymentStrategy(new BankCardStrategy("6222021234567890", "123456"));
        context.executePayment(300.0);
    }
}

输出结果:

======== 支付开始 ========
支付方式:支付宝
使用支付宝支付:100.0 元
正在调用支付宝API...
支付宝支付成功!
======== 支付结束 ========


======== 支付开始 ========
支付方式:微信支付
使用微信支付:200.0 元
正在调用微信支付API...
微信支付成功!
======== 支付结束 ========


======== 支付开始 ========
支付方式:银行卡
使用银行卡支付:300.0 元
卡号:6222****7890
正在验证银行卡信息...
银行卡支付成功!
======== 支付结束 ========

代码解析

  1. 策略接口:定义了统一的支付接口,所有支付方式都遵循这个接口
  2. 具体策略类:每个支付方式独立实现,互不影响
  3. 上下文类:负责管理策略,提供统一的调用入口
  4. 客户端:可以根据需要动态切换支付方式,无需修改调用代码

进阶示例:排序算法策略

让我们看一个更经典的例子:排序算法的策略模式实现。

定义策略接口

public interface SortStrategy {
    /**
     * 排序算法
     */
    <T extends Comparable<T>> void sort(T[] array);
    
    /**
     * 获取算法名称
     */
    String getName();
}

实现具体策略

快速排序:

public class QuickSortStrategy implements SortStrategy {
    
    @Override
    public <T extends Comparable<T>> void sort(T[] array) {
        if (array == null || array.length == 0) {
            return;
        }
        quickSort(array, 0, array.length - 1);
    }
    
    private <T extends Comparable<T>> void quickSort(T[] array, int left, int right) {
        if (left < right) {
            int pivotIndex = partition(array, left, right);
            quickSort(array, left, pivotIndex - 1);
            quickSort(array, pivotIndex + 1, right);
        }
    }
    
    private <T extends Comparable<T>> int partition(T[] array, int left, int right) {
        T pivot = array[right];
        int i = left - 1;
        
        for (int j = left; j < right; j++) {
            if (array[j].compareTo(pivot) <= 0) {
                i++;
                swap(array, i, j);
            }
        }
        swap(array, i + 1, right);
        return i + 1;
    }
    
    private <T> void swap(T[] array, int i, int j) {
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
    
    @Override
    public String getName() {
        return "快速排序";
    }
}

冒泡排序:

public class BubbleSortStrategy implements SortStrategy {
    
    @Override
    public <T extends Comparable<T>> void sort(T[] array) {
        if (array == null || array.length == 0) {
            return;
        }
        
        int n = array.length;
        for (int i = 0; i < n - 1; i++) {
            for (int j = 0; j < n - i - 1; j++) {
                if (array[j].compareTo(array[j + 1]) > 0) {
                    swap(array, j, j + 1);
                }
            }
        }
    }
    
    private <T> void swap(T[] array, int i, int j) {
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
    
    @Override
    public String getName() {
        return "冒泡排序";
    }
}

排序上下文

public class SortContext {
    
    private SortStrategy sortStrategy;
    
    public SortContext(SortStrategy sortStrategy) {
        this.sortStrategy = sortStrategy;
    }
    
    public void setSortStrategy(SortStrategy sortStrategy) {
        this.sortStrategy = sortStrategy;
    }
    
    public <T extends Comparable<T>> void executeSort(T[] array) {
        System.out.println("使用算法:" + sortStrategy.getName());
        long startTime = System.currentTimeMillis();
        
        sortStrategy.sort(array);
        
        long endTime = System.currentTimeMillis();
        System.out.println("排序耗时:" + (endTime - startTime) + "ms");
    }
}

使用示例

public class SortDemo {
    public static void main(String[] args) {
        Integer[] array = {64, 34, 25, 12, 22, 11, 90};
        
        SortContext context = new SortContext(new QuickSortStrategy());
        System.out.println("原始数组:" + Arrays.toString(array));
        context.executeSort(array);
        System.out.println("排序结果:" + Arrays.toString(array));
        
        // 切换排序算法
        Integer[] array2 = {64, 34, 25, 12, 22, 11, 90};
        context.setSortStrategy(new BubbleSortStrategy());
        context.executeSort(array2);
        System.out.println("排序结果:" + Arrays.toString(array2));
    }
}

策略工厂模式

在实际应用中,我们通常会结合工厂模式来创建策略对象,避免客户端直接创建具体策略类。

// 传统的静态代码块方式(不推荐):
/*
public class PaymentStrategyFactory {
    
    private static final Map<String, PaymentStrategy> strategies = new HashMap<>();
    
    static {
        strategies.put("ALIPAY", new AlipayStrategy());
        strategies.put("WECHAT", new WechatPayStrategy());
        // 银行卡需要参数,不能预先创建
    }
    
    public static PaymentStrategy getStrategy(String type) {
        PaymentStrategy strategy = strategies.get(type);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的支付方式:" + type);
        }
        return strategy;
    }
    
    public static PaymentStrategy getBankCardStrategy(String cardNumber, String password) {
        return new BankCardStrategy(cardNumber, password);
    }
    
    public static void registerStrategy(String type, PaymentStrategy strategy) {
        strategies.put(type, strategy);
    }
}
*/

// Spring优化方式(推荐):
// 1. 策略工厂类
@Component
public class PaymentStrategyFactory {

    @Autowired
    private Map<String, PaymentStrategy> strategies;

    public PaymentStrategy getStrategy(String type) {
        PaymentStrategy strategy = strategies.get(type);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的支付方式:" + type);
        }
        return strategy;
    }

    public PaymentStrategy getBankCardStrategy(String cardNumber, String password) {
        return new BankCardStrategy(cardNumber, password);
    }

    public Map<String, PaymentStrategy> getAllStrategies() {
        return strategies;
    }
}

// 2. 支付宝策略类
@Component("alipay")
public class AlipayStrategy implements PaymentStrategy {
    
    @Override
    public boolean pay(double amount) {
        System.out.println("使用支付宝支付:" + amount + " 元");
        System.out.println("正在调用支付宝API...");
        System.out.println("支付宝支付成功!");
        return true;
    }
    
    @Override
    public String getPaymentName() {
        return "支付宝";
    }
}

// 3. 微信支付策略类
@Component("weixin")
public class WechatPayStrategy implements PaymentStrategy {
    
    @Override
    public boolean pay(double amount) {
        System.out.println("使用微信支付:" + amount + " 元");
        System.out.println("正在调用微信支付API...");
        System.out.println("微信支付成功!");
        return true;
    }
    
    @Override
    public String getPaymentName() {
        return "微信支付";
    }
}

使用工厂:

// 传统方式调用
/*
public class PaymentWithFactoryDemo {
    public static void main(String[] args) {
        PaymentContext context = new PaymentContext(
            PaymentStrategyFactory.getStrategy("ALIPAY")
        );
        context.executePayment(100.0);
    }
}
*/

// Spring方式调用
@SpringBootApplication
public class PaymentApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(PaymentApplication.class, args);

        PaymentStrategyFactory factory = context.getBean(PaymentStrategyFactory.class);
        
        // 使用支付宝支付
        PaymentStrategy alipayStrategy = factory.getStrategy("支付宝");
        PaymentContext paymentContext = new PaymentContext();
        paymentContext.setPaymentStrategy(alipayStrategy);
        paymentContext.executePayment(100.0);

        // 使用微信支付
        PaymentStrategy wechatStrategy = factory.getStrategy("微信支付");
        paymentContext.setPaymentStrategy(wechatStrategy);
        paymentContext.executePayment(200.0);

        // 使用银行卡支付
        PaymentStrategy bankCardStrategy = factory.getBankCardStrategy("6222021234567890", "123456");
        paymentContext.setPaymentStrategy(bankCardStrategy);
        paymentContext.executePayment(300.0);
    }
}

Spring优化优势:

  • 无需手动维护静态代码块初始化strategies
  • 通过@Component注解的value属性自定义beanName作为Map的key
  • 新增策略时只需添加@Component注解,自动注册到Map中
  • 利用Spring依赖注入,代码更简洁优雅
  • 符合开闭原则和Spring最佳实践

结合枚举使用策略

在 Java 中,可以使用枚举来简化策略模式的实现:

public enum PaymentType implements PaymentStrategy {
    
    ALIPAY {
        @Override
        public boolean pay(double amount) {
            System.out.println("支付宝支付:" + amount + " 元");
            return true;
        }
        
        @Override
        public String getPaymentName() {
            return "支付宝";
        }
    },
    
    WECHAT {
        @Override
        public boolean pay(double amount) {
            System.out.println("微信支付:" + amount + " 元");
            return true;
        }
        
        @Override
        public String getPaymentName() {
            return "微信支付";
        }
    };
}

应用场景

策略模式在实际开发中应用非常广泛,以下是一些典型场景:

1. 支付系统

  • 支持多种支付方式:支付宝、微信、银行卡、PayPal等
  • 根据用户选择动态切换支付方式
  • 新增支付方式时无需修改现有代码

2. 数据导出

  • 支持多种导出格式:Excel、CSV、PDF、JSON
  • 不同格式有不同的导出逻辑,但接口统一

3. 折扣计算

  • 不同会员等级有不同的折扣策略
  • 促销活动期间使用特殊的折扣策略
  • 可以动态组合多种折扣策略

4. 路由算法

  • 负载均衡:轮询、随机、加权轮询、一致性哈希
  • 根据系统负载动态切换路由策略

5. 加密算法

  • 支持多种加密算法:AES、DES、RSA
  • 根据安全级别选择不同的加密策略

6. 数据验证

  • 不同场景使用不同的验证规则
  • 可配置的验证策略

7. 日志记录

  • 支持多种日志输出:文件、数据库、消息队列
  • 不同环境使用不同的日志策略

优点与缺点

优点

  1. 开闭原则:新增策略无需修改现有代码,只需新增一个策略类
  2. 避免条件语句:消除大量的 if-else 或 switch 语句,代码更清晰
  3. 提高封装性:每个策略独立封装,职责单一
  4. 易于扩展:新增策略不影响其他策略,扩展性强
  5. 运行时切换:可以在运行时动态切换算法,灵活性高
  6. 易于测试:每个策略可以独立测试,互不影响

缺点

  1. 客户端必须了解策略:客户端需要了解所有策略的区别,才能选择合适的策略
  2. 策略类数量增加:每个策略都是一个类,可能导致类数量增多
  3. 策略选择问题:如果策略数量很多,选择合适的策略可能变得复杂
  4. 通信开销:如果策略之间需要共享数据,可能增加通信开销

最佳实践

1. 使用工厂模式创建策略

避免客户端直接创建具体策略类,使用工厂模式统一管理策略的创建:

public class StrategyFactory {
    private Map<String, Strategy> strategies;
    
    public Strategy getStrategy(String type) {
        return strategies.get(type);
    }
}

2. 结合配置文件使用

将策略配置在外部文件中,实现动态加载:

# application.properties
payment.default.strategy=ALIPAY
payment.enabled.strategies=ALIPAY,WECHAT,BANKCARD

3. 使用依赖注入

在 Spring 等框架中,使用依赖注入自动装配策略:

@Component
public class PaymentContext {
    private final Map<String, PaymentStrategy> strategies;
    
    @Autowired
    public PaymentContext(List<PaymentStrategy> strategyList) {
        this.strategies = strategyList.stream()
            .collect(Collectors.toMap(
                PaymentStrategy::getPaymentName,
                Function.identity()
            ));
    }
}

4. 提供默认策略

为客户端提供默认策略,降低使用门槛:

public class PaymentContext {
    private PaymentStrategy strategy = new AlipayStrategy(); // 默认策略
    
    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }
}

5. 策略不可变

策略类应该是无状态的,或者状态不可变,确保线程安全:

public class QuickSortStrategy implements SortStrategy {
    // 无成员变量,线程安全
}

6. 提供策略选择帮助

当策略较多时,提供策略选择的辅助方法:

public class PaymentContext {
    public void showAvailableStrategies() {
        System.out.println("可用的支付方式:");
        strategies.forEach((key, value) -> {
            System.out.println("- " + key + ": " + value.getDescription());
        });
    }
    
    public PaymentStrategy recommendStrategy(double amount) {
        // 根据金额推荐最合适的支付方式
        if (amount > 10000) {
            return getStrategy("BANKCARD");
        } else {
            return getStrategy("ALIPAY");
        }
    }
}

策略模式 vs 其他模式

vs 模板模式

  • 策略模式:通过组合实现,整个算法可以替换
  • 模板模式:通过继承实现,算法骨架固定,部分步骤可变
  • 选择依据:如果算法整体变化,选策略模式;如果算法骨架不变,部分步骤变化,选模板模式

vs 状态模式

  • 策略模式:客户端主动选择策略,策略之间相互独立
  • 状态模式:状态自动切换,状态之间有转换关系
  • 选择依据:如果算法相互独立,选策略模式;如果对象行为随状态变化,选状态模式

vs 命令模式

  • 策略模式:关注算法的封装和切换
  • 命令模式:关注请求的封装和撤销
  • 选择依据:如果需要封装算法,选策略模式;如果需要封装请求,选命令模式

vs 简单工厂模式

  • 策略模式:行为型模式,关注算法切换
  • 简单工厂模式:创建型模式,关注对象创建
  • 结合使用:策略模式常与工厂模式结合使用

实际应用案例

Spring 框架中的策略模式

ResourceLoader:

// Spring 的资源加载策略
ResourceLoader loader = new DefaultResourceLoader();
Resource resource1 = loader.getResource("classpath:application.properties");
Resource resource2 = loader.getResource("file:/path/to/file.txt");
Resource resource3 = loader.getResource("https://example.com/file.txt");

InstantiationStrategy:

// Spring Bean 实例化策略
public interface InstantiationStrategy {
    Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner);
}

JDK 中的策略模式

Comparator:

// 排序策略
Arrays.sort(array, (a, b) -> a.compareTo(b));  // 升序
Arrays.sort(array, (a, b) -> b.compareTo(a));  // 降序

ThreadPoolExecutor 的拒绝策略:

// 不同的拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    corePoolSize, 
    maxPoolSize,
    keepAliveTime,
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(),
    new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略
);

组合模式:策略模式 + 责任链模式

在实际应用中,策略模式常与责任链模式结合使用:

public class DiscountCalculator {
    private List<DiscountStrategy> strategies;
    
    public double calculate(double originalPrice) {
        double price = originalPrice;
        for (DiscountStrategy strategy : strategies) {
            price = strategy.apply(price);
        }
        return price;
    }
    
    public void addStrategy(DiscountStrategy strategy) {
        strategies.add(strategy);
    }
}

// 使用
DiscountCalculator calculator = new DiscountCalculator();
calculator.addStrategy(new MemberDiscountStrategy());
calculator.addStrategy(new PromotionDiscountStrategy());
calculator.addStrategy(new CouponDiscountStrategy());

double finalPrice = calculator.calculate(1000.0);

总结

策略模式是一种简单而强大的设计模式,它通过将算法封装成独立的类,实现了算法的定义、实现和使用的分离。在实际开发中,当我们需要根据不同情况执行不同算法,并且算法可能频繁变化时,策略模式是一个很好的选择。

核心要点:

  1. 策略接口定义算法的公共接口
  2. 具体策略类实现不同的算法
  3. 上下文类负责管理策略的切换
  4. 客户端可以动态选择和切换策略
  5. 结合工厂模式可以简化策略的创建和管理

使用场景:

  1. 多个类只在行为上有差异,可以使用策略模式动态选择行为
  2. 需要在运行时选择算法的不同变体
  3. 避免使用复杂的条件语句来选择行为
  4. 算法经常变化,需要灵活扩展

记住: 策略模式不是银弹,要根据实际场景选择是否使用。如果算法简单、变化不大,直接使用条件语句可能更简单明了。策略模式适用于算法复杂、变化频繁、需要灵活扩展的场景。

参考资料

  • 《设计模式:可复用面向对象软件的基础》- GoF
  • 《Head First 设计模式》
  • 《Effective Java》(Third Edition)
  • Spring Framework Documentation
  • Java API Documentation

作者:leejie
日期:2026年
标签:Java、设计模式、策略模式、架构设计


文章作者: Leejie
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Leejie !
评论
  目录