设计模式篇:灵活多变的策略模式

发布于:2025-07-06 ⋅ 阅读:(15) ⋅ 点赞:(0)

引言:从现实世界到代码世界的面向对象

在商业策略制定中,企业会根据市场环境选择不同的竞争策略;在军事行动中,指挥官会根据敌情选择不同的战术;在游戏对战中,玩家会根据局势调整作战方式。这种根据情境选择不同行为的模式,在软件设计中同样普遍存在。策略模式(Strategy Pattern)正是为解决这类问题而生的经典设计模式。

想象你正在使用导航软件规划路线。同一个目的地,你可以选择:

  • 最快路线:优先考虑时间

  • 最短距离:不考虑路况,只求距离最短

  • 避开高速:宁愿慢些也要省过路费

  • 经济路线:平衡时间和费用

导航软件如何优雅地实现这些不同的路线计算算法?这就是策略模式大显身手的地方。作为行为型设计模式的代表,策略模式让我们能够定义一系列算法,并将每个算法封装起来,使它们可以相互替换。

一、策略模式解决了什么问题?

先看一个常见的反例:

public class Navigator {
    public void buildRoute(String routeType) {
        if ("FASTEST".equals(routeType)) {
            // 复杂的最快路线算法
            System.out.println("计算最快路线...考虑实时路况");
        } else if ("SHORTEST".equals(routeType)) {
            // 最短距离算法
            System.out.println("计算最短距离...忽略路况");
        } else if ("ECONOMIC".equals(routeType)) {
            // 经济路线算法
            System.out.println("计算经济路线...平衡时间和费用");
        }
        // 每新增一种路线类型,就要修改这里
    }
}

这种实现方式存在几个明显问题:

  1. 违反开闭原则:新增算法需要修改原有类

  2. 代码臃肿:所有算法堆积在一个类中

  3. 难以维护:随着算法增加,条件判断越来越复杂

  4. 复用困难:相同算法难以在其他地方复用

策略模式正好可以解决这些问题。

二、策略模式的核心结构

策略模式在GoF《设计模式》中的正式定义:

定义一系列算法,将每个算法封装起来,并使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户端。

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

  1. Context(上下文):维护对策略对象的引用,负责将客户端请求委托给当前策略

  2. Strategy(策略接口):定义所有支持算法的公共接口

  3. ConcreteStrategy(具体策略):实现策略接口的具体算法类

1. 基础实现

// 策略接口
interface RouteStrategy {
    void buildRoute(Point start, Point end);
}

// 具体策略:最快路线
class FastestRouteStrategy implements RouteStrategy {
    @Override
    public void buildRoute(Point start, Point end) {
        System.out.println("计算最快路线...考虑实时路况");
        // 实际算法实现
    }
}

// 具体策略:最短距离
class ShortestRouteStrategy implements RouteStrategy {
    @Override
    public void buildRoute(Point start, Point end) {
        System.out.println("计算最短距离...忽略路况");
        // 实际算法实现
    }
}

// 上下文
class Navigator {
    private RouteStrategy strategy;
    
    public void setStrategy(RouteStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void buildRoute(Point start, Point end) {
        if (strategy != null) {
            strategy.buildRoute(start, end);
        } else {
            System.out.println("请先设置路线策略");
        }
    }
}

// 使用示例
public class Client {
    public static void main(String[] args) {
        Navigator navigator = new Navigator();
        
        // 使用最快路线策略
        navigator.setStrategy(new FastestRouteStrategy());
        navigator.buildRoute(pointA, pointB);
        
        // 切换为最短距离策略
        navigator.setStrategy(new ShortestRouteStrategy());
        navigator.buildRoute(pointA, pointB);
    }
}

三、Spring中的策略模式实践

在Spring应用中,我们可以利用DI(依赖注入)更加优雅地实现策略模式:

// 策略接口
public interface DiscountStrategy {
    BigDecimal applyDiscount(BigDecimal amount);
    
    String getStrategyName();
}

// 具体策略:会员折扣
@Service
public class MemberDiscountStrategy implements DiscountStrategy {
    @Override
    public BigDecimal applyDiscount(BigDecimal amount) {
        return amount.multiply(new BigDecimal("0.9"));
    }
    
    @Override
    public String getStrategyName() {
        return "MEMBER_DISCOUNT";
    }
}

// 具体策略:节日折扣
@Service
public class FestivalDiscountStrategy implements DiscountStrategy {
    @Override
    public BigDecimal applyDiscount(BigDecimal amount) {
        return amount.multiply(new BigDecimal("0.8"));
    }
    
    @Override
    public String getStrategyName() {
        return "FESTIVAL_DISCOUNT";
    }
}

// 策略上下文
@Service
public class DiscountContext {
    private final Map<String, DiscountStrategy> strategyMap;
    
    @Autowired
    public DiscountContext(List<DiscountStrategy> strategies) {
        this.strategyMap = strategies.stream()
            .collect(Collectors.toMap(
                DiscountStrategy::getStrategyName,
                Function.identity()
            ));
    }
    
    public BigDecimal applyDiscount(String strategyName, BigDecimal amount) {
        DiscountStrategy strategy = strategyMap.get(strategyName);
        if (strategy == null) {
            throw new IllegalArgumentException("未知的折扣策略");
        }
        return strategy.applyDiscount(amount);
    }
}

// 控制器使用
@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private DiscountContext discountContext;
    
    @PostMapping("/checkout")
    public ResponseEntity<BigDecimal> checkout(
            @RequestParam String discountType,
            @RequestParam BigDecimal amount) {
        BigDecimal finalAmount = discountContext.applyDiscount(discountType, amount);
        return ResponseEntity.ok(finalAmount);
    }
}

Spring策略模式优势

  1. 自动收集所有策略实现

  2. 策略与上下文完全解耦

  3. 方便扩展新策略

  4. 策略可以享受Spring的所有特性(AOP、依赖注入等)

四、策略模式的性能考量

4.1 策略对象的创建成本

对于高频调用的策略,需要考虑策略对象的创建方式:

  1. 每次创建新实例:简单但可能产生GC压力

  2. 策略对象复用:适合无状态的策略

  3. 对象池技术:适用于创建成本高的策略

4.2 策略选择的效率

策略选择方式 时间复杂度 适用场景
if-else/switch O(n) 策略数量少(5个以下)
Map查找 O(1) 策略数量多
策略链 O(n) 需要依次尝试策略

五、策略模式的优缺点分析 

优势

  1. 开闭原则:无需修改已有代码即可新增策略

  2. 消除条件语句:替代大量的if-else或switch-case

  3. 提高可测试性:每个策略可以独立测试

  4. 运行时灵活性:支持动态切换算法

  5. 代码复用:不同上下文可以共享策略

局限性

  1. 客户端必须了解策略:需要知道不同策略的区别

  2. 策略类增多:可能增加系统中类的数量

  3. 通信开销:策略与上下文可能需要交换数据

  4. 不适合简单算法:可能会过度设计简单场景

六、策略模式与其他模式的关系

6.1与工厂模式的区别

模式 关注点 应用阶段 主要作用
工厂模式 对象创建 初始化阶段 隐藏创建逻辑
策略模式 行为算法 运行时 封装可互换的行为

6.2 与状态模式的对比

相似点:都有上下文和多个实现类
不同点:

  • 状态模式:状态转换通常由上下文内部管理

  • 策略模式:策略选择通常由客户端控制

最后,策略模式不仅仅是一种编码技巧,它体现了"分而治之"的古老智慧。通过将复杂多变的行为分解为独立的策略单元,我们获得了:

  1. 清晰的边界:每个策略只关注自己的算法

  2. 灵活的组合:可以像乐高积木一样搭配策略

  3. 可控的变化:算法变化被隔离在策略内部

正如《孙子兵法》所言:"兵无常势,水无常形",软件设计也应该能够随需而变。策略模式正是帮助我们实现这种灵活性的利器。

设计模式的最高境界是"无招胜有招"——不是机械地套用模式,而是理解其思想后自然地融入设计中。