文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

深入浅出设计模式【二十一、策略模式】

一、策略模式介绍

在软件开发中,我们经常需要根据不同的上下文或条件执行不同的算法或业务规则。例如,一个电商系统可能需要支持多种支付方式(支付宝、微信支付、信用卡)、多种折扣策略(满减、百分比折扣、无折扣)或多种排序算法(按价格、按销量、按评分)。

一种直观但笨拙的实现方式是使用条件语句(if-elseswitch-case)在主业务逻辑中判断并调用不同的算法。这种方法的缺点非常明显:

  1. 违反开闭原则 (OCP): 增加新算法或修改现有算法需要修改包含条件判断的主类。
  2. 代码臃肿且难以维护: 主类会充斥着各种算法的实现细节,变得庞大而复杂。
  3. 难以复用算法: 算法与使用它的上下文紧密耦合,无法独立复用。

策略模式通过将算法抽象算法实现分离来解决这些问题。它将每个算法封装到一个独立的策略类中,并使它们可以互相替换。客户端代码依赖于算法的抽象接口,而不是具体的实现,从而可以在运行时灵活地选择和切换算法。

二、核心概念与意图

  1. 核心概念

    • 策略接口 (Strategy): 定义所有支持的算法或策略的公共接口。上下文使用这个接口来调用具体策略定义的算法。
    • 具体策略 (Concrete Strategy): 实现策略接口,提供具体的算法实现。
    • 上下文 (Context): 持有一个策略对象的引用。上下文通常提供一个接口,允许客户端设置或切换策略。最后,上下文将客户端的请求委托给当前的策略对象执行。
  2. 意图

    • 定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换
    • 使得算法可以独立于使用它的客户端而变化
    • 消除条件语句,提供一种在运行时选择算法行为的灵活方式

三、适用场景剖析

策略模式在以下场景中非常有效:

  1. 一个系统需要在多种算法或策略中选择一种时: 例如,支付方式、折扣计算、数据验证规则、排序算法、压缩算法等。
  2. 需要避免暴露复杂的、与算法相关的数据结构时: 策略模式将算法的实现细节完全隐藏起来。
  3. 有多个仅在行为上稍有差别的类时: 使用策略模式可以避免创建大量庞大的条件语句。
  4. 希望算法能够独立于使用它的客户端,便于单元测试和复用: 每个策略都是一个独立的类,可以单独测试和复用。

四、UML 类图解析(Mermaid)

以下UML类图清晰地展示了策略模式的结构和角色间的关系:

strategy
Context
-strategy: Strategy
+setStrategy(strategy: Strategy)
+executeStrategy()
«interface»
Strategy
+execute(data)
ConcreteStrategyA
+execute(data)
ConcreteStrategyB
+execute(data)
ConcreteStrategyC
+execute(data)
  • Strategy (策略接口): 声明了算法的方法(通常是 executeapply),是所有具体策略的通用接口。
  • ConcreteStrategyA, ConcreteStrategyB, ConcreteStrategyC (具体策略): 实现了 Strategy 接口,提供了算法的具体实现。
  • Context (上下文)
    • 持有一个对策略对象的引用 (-strategy: Strategy)。
    • 通常提供一个设置器方法 (setStrategy) 允许客户端在运行时替换当前使用的策略对象。
    • 提供一个方法 (executeStrategy) 来委托策略执行算法。上下文本身不实现算法,而是将工作委派给连接的策略对象。
  • 客户端: 负责创建和配置上下文。客户端通常会创建一个具体策略对象,然后将其传递给上下文。之后,客户端通过上下文接口触发算法的执行。

五、各种实现方式及其优缺点

策略模式的实现非常灵活,主要取决于策略接口的设计和上下文的配置方式。

1. 标准实现(接口 + 类)

即上述UML所描述的方式,为每种算法定义一个实现策略接口的具体类。

// 1. Strategy Interface
public interface DiscountStrategy {
    double applyDiscount(double originalPrice);
}

// 2. Concrete Strategies
public class NoDiscountStrategy implements DiscountStrategy {
    @Override
    public double applyDiscount(double originalPrice) {
        return originalPrice;
    }
}

public class PercentageDiscountStrategy implements DiscountStrategy {
    private double percentage;

    public PercentageDiscountStrategy(double percentage) {
        this.percentage = percentage;
    }

    @Override
    public double applyDiscount(double originalPrice) {
        return originalPrice * (1 - percentage / 100);
    }
}

public class FixedAmountDiscountStrategy implements DiscountStrategy {
    private double discountAmount;

    public FixedAmountDiscountStrategy(double discountAmount) {
        this.discountAmount = discountAmount;
    }

    @Override
    public double applyDiscount(double originalPrice) {
        double result = originalPrice - discountAmount;
        return result > 0 ? result : 0; // Ensure price is not negative
    }
}

// 3. Context
public class ShoppingCart {
    private DiscountStrategy discountStrategy;
    private double totalAmount;

    public ShoppingCart(double totalAmount) {
        this.totalAmount = totalAmount;
        this.discountStrategy = new NoDiscountStrategy(); // Default strategy
    }

    public void setDiscountStrategy(DiscountStrategy discountStrategy) {
        this.discountStrategy = discountStrategy;
    }

    public double checkout() {
        return discountStrategy.applyDiscount(totalAmount);
    }
}

// 4. Client
public class Client {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart(100.0);

        // Client selects and sets the strategy at runtime
        cart.setDiscountStrategy(new PercentageDiscountStrategy(10.0)); // 10% off
        System.out.println("Price after 10% discount: " + cart.checkout());

        cart.setDiscountStrategy(new FixedAmountDiscountStrategy(20.0)); // $20 off
        System.out.println("Price after $20 discount: " + cart.checkout());
    }
}
  • 优点
    • 符合开闭原则: 可以轻松添加新的策略,而无需修改上下文或客户端代码。
    • 避免条件语句: 消除了复杂的条件判断。
    • 算法复用和独立测试: 每个策略类可以独立测试和复用。
  • 缺点
    • 客户端必须了解所有策略: 客户端需要知道不同策略的区别,并负责选择和使用正确的策略。
    • 类数量增加: 如果策略很多,会产生大量的小类。(可与工厂模式结合缓解)

2. 函数式实现(Java 8+ Lambda / Method Reference)

如果策略接口是一个函数式接口(只包含一个抽象方法),可以利用Java 8的Lambda表达式或方法引用来极大地简化实现,避免创建大量具体的策略类。

// Strategy Interface remains the same (it's a functional interface)
@FunctionalInterface
public interface DiscountStrategy {
    double applyDiscount(double originalPrice);
}

// Context remains the same
public class ShoppingCart {
    private DiscountStrategy discountStrategy;
    // ... other fields and methods

    public void setDiscountStrategy(DiscountStrategy discountStrategy) {
        this.discountStrategy = discountStrategy;
    }
}

// Client uses Lambdas or Method References
public class Client {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart(100.0);

        // Using a lambda expression
        cart.setDiscountStrategy(price -> price * 0.9); // 10% off
        System.out.println("Price with lambda discount: " + cart.checkout());

        // Using a method reference (if a method exists elsewhere)
        // cart.setDiscountStrategy(this::calculateDiscount);

        // Using a variable for reuse
        DiscountStrategy fixedDiscount = price -> {
            double result = price - 20;
            return result > 0 ? result : 0;
        };
        cart.setDiscountStrategy(fixedDiscount);
        System.out.println("Price with fixed discount: " + cart.checkout());
    }
}
  • 优点
    • 极其简洁: 无需为每个策略创建单独的类,代码更紧凑。
    • 灵活直观: 策略逻辑可以在使用处就地定义。
  • 缺点
    • 局限性: 适用于简单的、无状态的策略。如果策略需要复杂的配置或多个方法,标准的类实现仍然更合适。
    • 可读性: 复杂的逻辑写在Lambda中可能降低可读性。

六、最佳实践

  1. 优先使用组合而非继承: 策略模式是“组合优于继承”原则的经典案例。上下文拥有(has-a)一个策略,而不是(is-a)某种策略。
  2. 与工厂模式结合: 当策略的选择逻辑比较复杂,或者不希望客户端直接依赖具体策略时,可以引入一个策略工厂。客户端只需要告诉工厂所需策略的“类型”或“key”,由工厂负责创建并返回正确的策略对象。这进一步解耦了客户端和具体策略。
    public class DiscountStrategyFactory {
        private Map<String, DiscountStrategy> strategies = new HashMap<>();
    
        public DiscountStrategyFactory() {
            strategies.put("NO_DISCOUNT", new NoDiscountStrategy());
            strategies.put("PERCENTAGE_10", new PercentageDiscountStrategy(10));
            // ... register more strategies
        }
    
        public DiscountStrategy getStrategy(String discountCode) {
            DiscountStrategy strategy = strategies.get(discountCode);
            if (strategy == null) {
                throw new IllegalArgumentException("Unknown discount code");
            }
            return strategy;
        }
    }
    
  3. 考虑使用枚举策略 (Enum Strategy): 如果策略是固定的、有限的,并且不需要在运行时动态添加,可以使用枚举来实现策略模式。每个枚举实例代表一个策略,并实现策略接口。
    public enum DiscountStrategyEnum implements DiscountStrategy {
        NO_DISCOUNT {
            @Override
            public double applyDiscount(double price) { return price; }
        },
        PERCENTAGE_10 {
            @Override
            public double applyDiscount(double price) { return price * 0.9; }
        };
    
        // ... other enum strategies
    }
    
  4. 明确与状态模式的区别
    • 策略模式: 客户端主动选择策略。策略之间通常是独立的,不关心状态转换。目的是灵活替换算法
    • 状态模式: 状态转换通常由内部状态自动触发,客户端不感知。状态对象知道下一个可能的状态。目的是管理状态相关的行为

七、在开发中的演变和应用

策略模式的思想是现代软件架构中可插拔架构依赖注入的核心:

  1. Spring Framework 的依赖注入 (DI): Spring的IoC容器本质上是一个巨大的策略模式应用。你定义一个接口(策略),并可能有多个实现(具体策略)。通过 @Autowired 注入接口时,Spring根据配置(如 @Primary, @Qualifier) 或Profile来决定注入哪个具体的策略实现。这使得业务代码只依赖于接口,实现可以轻松替换。
  2. Java Collections.sort() 和 ComparatorComparator 接口就是一个策略接口,用于定义排序算法。Collections.sort(list, comparator) 方法接受一个 Comparator 策略对象,从而允许客户端在运行时指定排序规则,而不是使用元素默认的 Comparable 实现。这是策略模式的教科书级案例。
  3. Java Servlet 过滤器 (Filter) 和 Spring 拦截器 (Interceptor): 虽然结构上更偏向责任链,但其可插拔的思想与策略模式相通。你可以提供不同的过滤/拦截策略,并配置它们应用到Web请求上。
  4. 支付网关集成: 系统定义统一的支付接口 (PaymentService),而支付宝、微信支付、Stripe等分别提供实现。通过配置决定使用哪个支付策略,新增支付方式只需添加新实现,无需修改核心业务逻辑。

八、真实开发案例(Java语言内部、知名开源框架、工具)

  1. java.util.Comparator

    • 这是JDK中最经典的策略模式应用。
    • 策略接口Comparator<T>
    • 具体策略: 任何实现了 Comparator 的类,或者通过Lambda表达式、方法引用创建的 comparator。
    • 上下文Collections.sort(List<T> list, Comparator<? super T> c), List.sort(Comparator<? super T> c), Arrays.sort(T[] a, Comparator<? super T> c) 等方法。
    • 客户端: 调用排序方法的代码,负责提供具体的比较策略。
  2. Spring Framework的 PlatformTransactionManager

    • Spring的事务抽象是策略模式的完美体现。
    • 策略接口PlatformTransactionManager (定义了 getTransaction, commit, rollback 等方法)。
    • 具体策略DataSourceTransactionManager (用于JDBC), JpaTransactionManager (用于JPA), JtaTransactionManager (用于JTA分布式事务), HibernateTransactionManager 等。
    • 上下文: Spring的事务拦截器和管理器,它们只依赖于 PlatformTransactionManager 接口。
    • 客户端: 通常是Spring框架本身,根据你的配置(如 @Transactional, XML)来选择具体的事务管理器策略。
  3. Spring Security 的 AuthenticationManager

    • Spring Security 的认证过程也使用了策略模式。
    • 不同的认证方式(表单登录、JWT、OAuth2、LDAP)可能对应不同的 AuthenticationProvider (策略)。AuthenticationManager (通常是一个 ProviderManager) 会遍历一个 AuthenticationProvider 列表,直到找到一个能处理当前认证请求的Provider。
  4. Java 的 TimeZone 类 (概念上)

    • 虽然不像前几个例子那样有明确的接口,但 TimeZone.getTimeZone(String ID) 方法允许你通过ID(如 “GMT+8”, “America/New_York”)获取不同的时区对象,每个对象计算时间偏移的规则不同,这也体现了策略选择的思想。

九、总结

方面总结
模式类型行为型设计模式
核心意图定义一系列算法,封装它们,并使它们可以互相替换,让算法独立于使用它的客户端。
关键角色策略接口(Strategy), 具体策略(ConcreteStrategy), 上下文(Context)
核心机制1. 抽象接口: 定义算法族。
2. 封装实现: 每个算法独立成类。
3. 委托调用: 上下文将请求委托给当前策略对象。
主要优点1. 完美符合开闭原则,易于扩展新算法。
2. 消除条件分支,代码清晰。
3. 算法可复用、可独立测试
4. 运行时灵活切换算法
主要缺点1. 客户端必须了解策略差异(可与工厂模式结合缓解)。
2. 策略类数量可能增多(可用Lambda缓解)。
3. 增加了对象和间接调用的开销(通常可忽略)。
适用场景系统需要在多种算法中选择;需要避免暴露复杂算法细节;有大量条件判断语句。
实现选择标准类实现: 功能强大,适合复杂策略。
函数式实现 (Lambda): 简洁灵活,适合简单策略。
最佳实践结合工厂模式选择策略;优先使用组合;明确与状态模式的区别。
现代应用依赖注入 (DI) 的理论基础,可插拔架构的核心思想。
真实案例Java Comparator (经典),Spring PlatformTransactionManager (工业级),支付网关集成

策略模式是应对算法变化和实现灵活性的终极武器。它通过将算法抽象化,使得系统架构更加清晰、灵活和健壮。其“组合优于继承”的思想深刻影响了现代框架的设计(如Spring),是实现开闭原则和依赖倒置原则的关键技术。掌握策略模式,意味着你掌握了构建可扩展、可维护系统的核心设计能力。从简单的排序比较器到复杂的事务管理,策略模式无处不在,是架构师和高级开发者工具箱中不可或缺的工具。

posted @ 2025-08-30 00:20  NeoLshu  阅读(4)  评论(0)    收藏  举报  来源