Java策略模式案例代码

wen java案例 2

从IF地狱到策略模式:一个订单折扣场景的重构实战(附完整案例代码)

目录导读

  • 为什么你需要策略模式? —— 从一段令人窒息的if-else代码说起
  • 策略模式核心解剖 —— 三个角色的职责与协作关系图
  • 实战案例:电商订单折扣系统 —— 完整Java代码演示(含UML逻辑)
  • 策略模式 vs 工厂模式 vs 状态模式 —— 一张表看懂区别与适用场景
  • 高频面试问答 —— 策略模式如何消除if-else?如何与Spring结合?
  • 避坑指南 —— 哪些场景千万别用策略模式

为什么你需要策略模式?从一段令人窒息的代码说起

假设你正在开发一个电商订单系统,产品经理提了需求:根据用户等级(普通、VIP、超级VIP)计算订单折扣,第一版代码,你可能会这样写:

Java策略模式案例代码

public double calculateDiscount(Order order) {
    String userLevel = order.getUser().getLevel();
    double amount = order.getAmount();
    if ("NORMAL".equals(userLevel)) {
        return amount * 0.95; // 95折
    } else if ("VIP".equals(userLevel)) {
        return amount * 0.90; // 9折
    } else if ("SUPER_VIP".equals(userLevel)) {
        return amount * 0.80; // 8折
    } else {
        return amount * 1.0; // 无折扣
    }
}

刚开始,这段代码简洁明了,但第二周,产品经理说:“新增‘钻石会员’,打75折,并且生日当天额外减50元。”第三周,“运营活动:双十一全场85折,叠加会员折扣。”第四周,“老客户回馈:消费满1000减200,但仅限普通用户。”

你的calculateDiscount方法已经变成上百行的if-else嵌套,每次改动,你都要小心翼翼,因为任何一行都会影响所有用户。这就是典型的“策略爆炸”问题——算法(折扣逻辑)和上下文(订单、用户)高度耦合。

策略模式 正是为了解决这类“多种算法可互换”的场景而生,它定义算法族,分别封装起来,让它们之间可以互相替换,且不影响调用方,简单说:把变化的算法抽出来,变成一个个独立的策略类


策略模式核心解剖:三个角色与协作关系

策略模式包含三个核心角色:

  1. 策略接口(Strategy):定义算法的公共接口,例如DiscountStrategy
  2. 具体策略(ConcreteStrategy):实现策略接口的具体算法类,例如VipDiscountStrategySuperVipDiscountStrategy
  3. 上下文(Context):持有策略接口的引用,负责维护对具体策略的调用,上下文不直接实现算法,而是将算法委托给策略对象。

协作流程如下:

客户端 → 上下文(Context)→ 策略接口(Strategy)→ 具体策略实现

关键点在于:上下文只依赖策略接口,不依赖任何具体策略,这样,新增算法时,只需新增具体策略类,无需修改上下文和客户端代码——满足开闭原则(OCP)。


实战案例:电商订单折扣系统(完整Java代码)

定义策略接口

public interface DiscountStrategy {
    /**
     * 计算折扣后的金额
     * @param order 订单对象(包含金额、用户信息等)
     * @return 实际应付金额
     */
    double calculate(Order order);
}

实现具体策略类

// 普通会员:95折
public class NormalDiscountStrategy implements DiscountStrategy {
    @Override
    public double calculate(Order order) {
        return order.getAmount() * 0.95;
    }
}
// VIP会员:9折
public class VipDiscountStrategy implements DiscountStrategy {
    @Override
    public double calculate(Order order) {
        return order.getAmount() * 0.90;
    }
}
// 超级VIP:8折
public class SuperVipDiscountStrategy implements DiscountStrategy {
    @Override
    public double calculate(Order order) {
        return order.getAmount() * 0.80;
    }
}
// 新增:钻石会员,75折(无需修改任何现有代码,只需新增一个类)
public class DiamondDiscountStrategy implements DiscountStrategy {
    @Override
    public double calculate(Order order) {
        return order.getAmount() * 0.75;
    }
}

定义上下文(Context)—— 订单服务

public class OrderService {
    private final DiscountStrategy discountStrategy;
    // 通过构造器注入策略对象
    public OrderService(DiscountStrategy discountStrategy) {
        this.discountStrategy = discountStrategy;
    }
    // 可选:提供setter方法用于运行时动态切换策略
    public void setDiscountStrategy(DiscountStrategy discountStrategy) {
        this.discountStrategy = discountStrategy;
    }
    public double checkout(Order order) {
        // 委托给策略对象计算折扣
        return discountStrategy.calculate(order);
    }
}

客户端使用示例

public class Client {
    public static void main(String[] args) {
        Order order = new Order();
        order.setAmount(1000.0);
        order.setUserLevel("VIP");
        // 根据用户等级创建对应策略
        DiscountStrategy strategy = null;
        String level = order.getUserLevel();
        if ("NORMAL".equals(level)) {
            strategy = new NormalDiscountStrategy();
        } else if ("VIP".equals(level)) {
            strategy = new VipDiscountStrategy();
        } else if ("SUPER_VIP".equals(level)) {
            strategy = new SuperVipDiscountStrategy();
        }
        OrderService service = new OrderService(strategy);
        double finalAmount = service.checkout(order);
        System.out.println("最终应付金额:" + finalAmount);
    }
}

注意:上面的客户端可能仍然有if-else来创建策略对象,这时候可以结合简单工厂Spring容器来彻底消除客户端分支,在Spring中,你可以将每个策略注册为Bean,然后通过Map<String, DiscountStrategy>自动注入。

Spring中优雅使用策略模式(进阶)

@Service
public class DiscountStrategyFactory {
    @Autowired
    Map<String, DiscountStrategy> strategyMap; // Spring会注入所有策略Bean,key为Bean名称
    public DiscountStrategy getStrategy(String discountType) {
        return strategyMap.get(discountType);
    }
}

这样,客户端只需要调用factory.getStrategy("vipDiscountStrategy")即可获取对应策略,彻底消除if-else。


策略模式 vs 工厂模式 vs 状态模式

模式 核心解决 关键区别 典型应用
策略模式 算法族的切换 上下文与算法解耦,客户端决定策略,算法可互换 打折、排序、压缩算法
工厂模式 对象的创建 封装创建逻辑,返回实例(可能对应多个策略) 替代直接new对象
状态模式 对象状态导致的内部行为变化 状态切换由对象内部触发,策略切换由外部触发 订单状态机(待付款→已付款→已发货)

关键一句话:策略模式关注“如何做”,工厂模式关注“创建什么”,状态模式关注“何时切换并把行为摊入状态”。

实战建议:当你在写if (type == A) doX; else if (type == B) doY;,并且分支里的逻辑超过3行且每个分支相互独立时,考虑策略模式,如果分支只是简单的对象创建,用工厂模式即可。


高频面试问答

Q1:策略模式一定消除了所有的if-else吗? A:不,策略模式消除了“算法内部”的条件判断,但客户端在“选择用哪个策略”时,可能还需要if-else或switch,完全消除需要结合工厂模式+注册表(如Spring的Map注入)或枚举。

Q2:策略模式与多态有什么区别? A:多态是面向对象的基本特性(方法重写),策略模式是多态的一种典型应用,策略模式强调“将算法封装为对象”,并且这些对象可以独立于客户端变化,你可以在不使用策略模式时就使用多态(如继承重写),但策略模式更强调“组合优于继承”,通过接口+组合实现算法的自由切换。

Q3:策略模式会造成类爆炸吗?如果策略很多怎么办? A:确实会增加类数量,解决方案包括:使用匿名内部类、Lambda表达式(参考现有策略接口,用DiscountStrategy s = order -> order.getAmount() * 0.95)或者Enum枚举内嵌策略逻辑,Lambda是Java 8中策略模式的极简写法,适合简单算法。

Q4:在Spring中,如何为同一接口的多个实现自动注入策略? A:通过@Autowired注入Map<String, Interface>,key是Bean的名称(类名首字母小写),value是实例,或者使用List<Interface>,然后你自行选择,推荐前者。


避坑指南:哪些场景千万别用策略模式

  1. 算法极少且几乎不变:比如只判断一个bool值然后返回固定折扣,不要套模式。
  2. 策略之间有公共逻辑且状态需要共享:策略模式强调算法独立,如果各策略依赖大量公共状态,不如使用模板方法模式。
  3. 客户端需要频繁切换策略且每次切换都涉及复杂配置:这时可考虑状态模式或构建者模式。
  4. 性能极端敏感:策略对象会增加一个间接调用层,通常可以忽略,但如果是在超高频循环中(如每秒百万次),建议谨慎。

策略模式是Java开发中“消除if-else”的最强工具之一,它通过封装算法,让代码更优雅、更符合开闭原则,但记住,它不是万能的,也不应该为了模式而模式,当你的代码中出现频繁的算法分支,并且这些算法未来可能扩展时,策略模式就是你的不二之选,配合Spring的IOC和Lambda,你可以写出高效且易于维护的代码。

核心金句:策略模式不是消灭变化,而是把变化封装起来,让变化不再波及全局。

抱歉,评论功能暂时关闭!