Java 策略模式(Strategy Pattern)-(二)

Java 策略模式中"设置策略"的深入解析

策略模式的核心在于将算法封装成独立对象,并通过上下文(Context)组合这些对象,从而实现算法的动态替换 。其中 "设置策略" 这一操作是连接客户端与具体算法的桥梁,也是整个模式能够灵活运转的关键。下面从概念、作用、运行机制、实际场景四个维度深入分析。


一、概念:什么是"设置策略"?

"设置策略"指上下文(Context)对外提供一种方式,使客户端能够将某个具体的策略对象(实现了策略接口)注入到上下文中。此后,上下文在执行核心方法时,会将工作委派给当前所持有的策略对象。

从代码形式上看,通常表现为:

  • 构造函数注入:在创建上下文时传入初始策略。

  • Setter 方法注入 :提供 setStrategy(Strategy s) 方法,允许运行时动态更改策略。

  • 方法参数注入:直接在调用上下文的核心方法时传入策略(不常单独使用,常与前面结合)。


二、作用:为什么要单独强调"设置策略"?

作用 说明
实现运行时动态切换 不用销毁上下文对象,只需调用 setStrategy() 即可更换算法,适用于用户交互、环境变化等场景。
解耦客户端与具体算法 客户端只需知道策略接口,无需关心算法实现细节;通过设置策略完成依赖注入。
遵循开闭原则 新增算法只需新增策略类,客户端通过设置新策略即可使用,上下文代码零修改。
增强测试性 可以方便地注入 Mock 策略对象进行单元测试,隔离真实算法。

三、运行机制与底层原理

3.1 依赖关系图
复制代码
客户端 (Client)
   │
   │ 1. 创建具体策略对象  │ 2. 设置策略(setStrategy)
   ▼                     ▼
上下文 (Context) ────持有────► 策略接口 (Strategy)
   │                              ▲
   │ 3. 执行算法                  │ 4. 多态调用
   ▼                              │
execute() ──委派调用──→ 具体策略 (ConcreteStrategyA/B...)
3.2 核心运行步骤(以 Setter 注入为例)
复制代码
// 1. 策略接口
public interface DiscountStrategy {
    double applyDiscount(double price);
}

// 2. 具体策略(满减、折扣)
public class FullReductionStrategy implements DiscountStrategy {
    private double threshold;
    private double reduction;
    public FullReductionStrategy(double threshold, double reduction) { ... }
    public double applyDiscount(double price) {
        return price >= threshold ? price - reduction : price;
    }
}

public class PercentageStrategy implements DiscountStrategy {
    private double percent; // 0~1
    public double applyDiscount(double price) { return price * (1 - percent); }
}

// 3. 上下文
public class ShoppingCart {
    private DiscountStrategy strategy;
    
    // 设置策略(关键操作)
    public void setStrategy(DiscountStrategy strategy) {
        this.strategy = strategy;
    }
    
    public double checkout(double originalPrice) {
        if (strategy == null) throw new IllegalStateException("未设置折扣策略");
        return strategy.applyDiscount(originalPrice);
    }
}

// 4. 客户端
public class Client {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        
        // 设置满减策略
        cart.setStrategy(new FullReductionStrategy(300, 50));
        System.out.println(cart.checkout(350)); // 输出 300
        
        // 动态切换为打折策略
        cart.setStrategy(new PercentageStrategy(0.2));
        System.out.println(cart.checkout(350)); // 输出 280
    }
}
3.3 底层机制详析
  1. 多态绑定

    • 上下文持有策略接口类型的引用(如 DiscountStrategy)。

    • 客户端传入的具体策略对象实现了接口,Java 运行时通过动态绑定调用实际的方法。

  2. 组合优于继承

    • 上下文 包含 策略对象(Has-A 关系),而不是通过继承算法基类来获得行为。这样可以在运行时改变"行为",而继承是静态的。
  3. 委派模型

    • 上下文本身不实现算法,当 checkout() 被调用时,它将计算工作委派 给所持有的策略对象的 applyDiscount() 方法。这符合"少用继承多用组合"和"委托优于实现"的原则。
  4. 对象间的松耦合

    • 策略对象独立于上下文,可以单独测试、修改、甚至动态创建(如通过工厂模式)。上下文只依赖抽象接口,不依赖具体实现。
3.4 不同"设置策略"方式的机制差异
注入方式 代码示例 运行机制特点 适用场景
构造函数注入 new Context(strategy) 策略在上下文创建时固定,不可变 策略在整个生命周期不变
Setter 方法注入 context.setStrategy(new Strategy()) 可以随时替换策略,上下文保持同一实例 需要动态切换策略(如用户选择支付方式)
方法参数注入 context.execute(new Strategy(), data) 每次执行都传入策略,上下文不保存状态 策略仅单次使用,无状态
配置/注解注入 @Autowired + @Qualifier(Spring) 容器管理策略对象的生命周期和注入 企业级应用,依赖 IoC 容器

四、实际场景深度分析

场景 1:电商订单价格计算(多折扣叠加之前的策略选择)

需求:用户下单时,系统根据用户等级、优惠券、活动类型等选择一种最优折扣策略(如会员折扣、满减券、限时打折)。

设置策略的时机

  • 用户在结算页面切换优惠方式时,后端接收到请求,动态调用 order.setDiscountStrategy() 切换策略。

  • 如果不使用策略模式,会写出大量 if (type==1){...} else if (type==2){...}

运行机制

  • 策略接口:PriceCalculator

  • 具体策略:VipDiscount, CouponDiscount, FlashSaleDiscount

  • 上下文:OrderContext,持有策略引用,提供 calculateTotal() 方法。

  • 客户端(Controller)根据用户选择的优惠类型,从工厂获取对应策略,然后设置到订单上下文中。

代码骨架示例

复制代码
// 策略接口
public interface PriceCalculator {
    BigDecimal calculate(Order order);
}

// 上下文
public class OrderContext {
    private PriceCalculator calculator;
    public void setCalculator(PriceCalculator calculator) { this.calculator = calculator; }
    public BigDecimal finalPrice(Order order) {
        return calculator.calculate(order);
    }
}

// Controller 层
@PostMapping("/checkout")
public Result checkout(@RequestBody CheckoutParam param) {
    OrderContext context = new OrderContext();
    PriceCalculator strategy = strategyFactory.getStrategy(param.getPromotionType());
    context.setCalculator(strategy);
    BigDecimal finalPrice = context.finalPrice(order);
    // ...
}
场景 2:文件导出格式选择(Excel、PDF、CSV)

需求:报表系统允许用户选择导出格式,点击按钮后生成对应格式的文件。

设置策略的时机

  • 用户在前端下拉框选择"导出为 PDF"或"导出为 Excel",后端收到请求后,根据格式字符串创建对应的导出策略,设置到导出上下文中。

优势

  • 新增一种导出格式(如 Word)只需要添加新的策略类和一个工厂分支,完全不需要修改导出流程代码。
场景 3:数据校验流水线(配合责任链模式)

虽然策略模式本身不强调顺序,但在校验场景中,常常动态设置校验策略:

复制代码
public class ValidatorContext {
    private ValidationStrategy strategy;
    public void setStrategy(ValidationStrategy strategy) { this.strategy = strategy; }
    public boolean validate(String input) { return strategy.validate(input); }
}

// 客户端根据字段类型设置不同校验策略
if (fieldType.equals("email")) {
    validator.setStrategy(new EmailValidationStrategy());
} else if (fieldType.equals("phone")) {
    validator.setStrategy(new PhoneValidationStrategy());
}
场景 4:游戏角色攻击方式

需求:游戏角色可以切换武器(剑、弓、法杖),每种武器有不同的攻击算法。

设置策略的时机

  • 玩家按快捷键或打开装备栏换武器时,游戏调用 character.setWeaponStrategy(new BowStrategy())

运行机制

  • 角色类(上下文)持有 AttackStrategy 接口。

  • 当角色攻击时,character.attack() 内部调用 currentStrategy.attack()

  • 换武器只需调用 setStrategy(),角色行为立即改变,无需重建角色对象。


五、高级话题:设置策略时的注意事项

5.1 线程安全

如果上下文被多线程共享,且策略对象是可变的(有状态),那么动态设置策略可能导致线程安全问题。解决方案:

  • 使用不可变策略对象。

  • 每次切换策略时创建新的上下文实例(请求作用域)。

  • 加锁或使用 volatile(如果策略引用需要立即可见)。

5.2 与工厂模式结合

避免客户端直接 new 具体策略,造成紧耦合。通常会:

  • 创建 StrategyFactory,根据类型枚举或字符串返回对应的策略实例。

  • 客户端只需传入类型标识,工厂负责创建策略对象并注入(或返回给客户端自行设置)。

5.3 策略缓存与享元模式

如果策略对象是无状态的,可以设计为单例或享元,减少对象创建开销。设置策略时复用同一个实例。

5.4 空策略模式(Null Strategy)

为了避免上下文中的策略为 null,可以定义一个 NullStrategy 实现策略接口,其方法不做任何操作或返回默认值。设置策略时默认注入该策略。


六、总结

维度 核心要点
概念 设置策略是向上下文注入具体算法对象的行为,通常通过构造函数、Setter 或参数传递实现。
作用 运行时动态切换算法、解耦客户端与实现、支持开闭原则、便于测试。
运行机制 基于多态、组合、委派;上下文保存策略接口引用,执行时委派给策略对象的具体方法。
实际场景 支付方式切换、折扣计算、文件导出、数据校验、游戏角色武器切换等。
最佳实践 与工厂模式结合、考虑线程安全、使用不可变策略、空策略避免 null。

理解"设置策略"这一动作的底层机制,能够帮助你在实际开发中更灵活地运用策略模式,写出易扩展、可维护的代码。当遇到算法族频繁变化或需要消除长串条件判断时,记得想起"向上下文设置一个策略"这个简单的操作。

相关推荐
摇滚侠1 小时前
CSDN AI 数字营销测评 营销组件
java
Royzst1 小时前
一、IO 概述
开发语言·python
Java_2017_csdn1 小时前
Java 策略模式(Strategy Pattern)-(一)
java·开发语言·策略模式
思茂信息1 小时前
CST对一种用于中型无人机 433MHz 通信的宽带共形贴片天线
开发语言·单片机·嵌入式硬件·平面·无人机·cst
plainGeekDev1 小时前
XML Shape/Selector → Kotlin 动态创建
android·java·kotlin
plainGeekDev1 小时前
Java 自定义 View → Kotlin 自定义 View
android·java·kotlin
石山代码1 小时前
java 反射
java·开发语言·tomcat
无限进步_1 小时前
【Linux】进度条:行缓冲区、\r 与 fflush 的实战
linux·服务器·开发语言·数据结构·后端