策略模式和 if-else 相比有什么好处?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

  1. 对代码设计原则的理解:面试官想考察你是否理解 “开闭原则”、“单一职责原则” 等核心设计思想,并能在实际场景中应用它们,而不仅仅是背诵概念。
  2. 重构与代码质量意识:考察你是否具备将 “坏味道” 代码(如庞大的 if-elseswitch 语句块)重构为更优结构的能力,以及你是否重视代码的可维护性、可测试性和可扩展性。
  3. 面向接口编程与多态的应用:你是否能熟练运用接口或抽象类,将行为抽象出来,并利用多态特性在运行时动态替换算法,从而降低耦合。
  4. 解决复杂问题的设计能力:面对不断变化的业务逻辑,你能否设计出具备良好扩展性的结构,从容应对未来需求的变更,避免牵一发而动全身。

核心答案

策略模式的核心好处在于它将 “做什么(行为)”“谁来做(上下文)” 解耦。

相比于硬编码的 if-else,它的主要优势有:

  1. 遵循开闭原则,易于扩展:新增一种策略(行为)时,只需增加一个新的策略类,无需修改上下文或其他策略类的代码,对现有系统影响最小。
  2. 避免复杂的条件判断:它消除了庞大的 if-elseswitch 分支,使代码结构更清晰、职责更单一,显著提升了可读性和可维护性。
  3. 提升代码复用性:每个策略都是独立的类,可以方便地在不同的上下文中复用。
  4. 便于单元测试:每个策略类可以独立进行单元测试,上下文也可以使用 Mock 策略进行测试,测试覆盖更全面、更容易。

深度解析

原理与对比

if-else 的问题场景:当处理某一类问题的算法或行为有多种,且未来可能频繁增减或修改时,将所有逻辑堆砌在一个方法中,会导致该方法臃肿不堪、难以阅读和维护。每次修改都需深入这个庞大分支的内部,极易引入错误。

策略模式的机制

  1. 定义一个公共的 策略接口,声明所有具体策略都必须实现的方法。
  2. 将每种不同的算法或行为封装成一个个实现了该接口的 具体策略类
  3. 上下文类 中持有一个策略接口的引用,而非具体的实现。通过组合的方式,将行为的执行委托给当前持有的策略对象。

这样,上下文的执行逻辑就与具体策略解耦了。改变行为只需更换策略对象,而非修改上下文代码。

代码示例

假设我们有一个订单折扣计算的需求,最初有普通会员、VIP会员两种折扣。

if-else 实现(问题版本):

public class OrderService {
    public double calculateDiscount(String userType, double price) {
        if ("NORMAL".equals(userType)) {
            return price * 0.95; // 普通会员95折
        } else if ("VIP".equals(userType)) {
            return price * 0.85; // VIP会员85折
        } else if ("SVIP".equals(userType)) { // 新增超级VIP
            return price * 0.75;
        } else {
            throw new IllegalArgumentException("Unknown user type");
        }
        // 未来再加类型,这里会越来越长...
    }
}

策略模式实现(优化版本):

// 1. 定义策略接口
public interface DiscountStrategy {
    double calculateDiscount(double price);
}

// 2. 实现具体策略
public class NormalMemberStrategy implements DiscountStrategy {
    @Override
    public double calculateDiscount(double price) {
        return price * 0.95;
    }
}

public class VipMemberStrategy implements DiscountStrategy {
    @Override
    public double calculateDiscount(double price) {
        return price * 0.85;
    }
}

// 3. 上下文(Context)
public class DiscountContext {
    private DiscountStrategy strategy;

    // 可以通过构造器或Setter注入策略
    public DiscountContext(DiscountStrategy strategy) {
        this.strategy = strategy;
    }

    public double calculate(double price) {
        // 将计算委托给当前策略对象
        return strategy.calculateDiscount(price);
    }
}

// 4. 客户端使用
public class Client {
    public static void main(String[] args) {
        double price = 100.0;

        // 根据用户类型动态选择策略
        String userType = "VIP";
        DiscountStrategy strategy;
        if ("VIP".equals(userType)) {
            strategy = new VipMemberStrategy();
        } else if ("NORMAL".equals(userType)) {
            strategy = new NormalMemberStrategy();
        } else {
            throw new IllegalArgumentException("Unknown user type");
        }

        DiscountContext context = new DiscountContext(strategy);
        double finalPrice = context.calculate(price);
        System.out.println("Final Price: " + finalPrice);
    }
}

最佳实践与常见误区

  • 最佳实践

    1. 结合工厂模式:客户端自己通过 if-else 选择策略,依然存在条件判断。更优的做法是使用 简单工厂Map 注册表 来根据类型获取策略对象,从而在客户端也消除条件判断。
    2. Spring 集成:在 Spring 项目中,可以将所有 DiscountStrategy 的实现类注入到一个 Map<String, DiscountStrategy> 中,key 为策略名称。使用时直接根据 key 从 Map 中获取,极其优雅。
    3. 无状态策略:确保策略类是无状态的,即不包含可变的成员变量。如果需要上下文信息,应通过方法参数传递。
  • 常见误区

    1. 过度设计:如果策略类型非常固定(比如只有 2-3 种),且未来几乎不可能变化,直接使用 if-else 或枚举反而是更简单清晰的选择。策略模式适用于算法经常变化或类型较多的场景。
    2. 混淆策略模式与状态模式:两者类图相似,但目的不同。策略模式是让客户端主动选择不同的算法;状态模式是对象内部状态改变时,其行为自动随之改变

总结

策略模式通过将可变的行为抽象并封装,以组合替代继承和条件分支,在算法时常变更或种类繁多的业务场景下,能极大地提升代码的清晰度、可维护性和扩展性。