全栈杂谈第24期:策略模式—灵活算法的艺术

22 阅读5分钟

模式起源:解决算法僵化难题

在软件系统开发中,我们经常遇到需要根据不同条件执行不同算法的场景。传统实现方式往往导致代码中出现大量的if-else或switch-case分支,这种硬编码方式存在三个显著缺陷:

  1. 违反开闭原则:新增算法需要修改现有代码
  2. 代码臃肿:条件分支导致方法体过度膨胀
  3. 复用困难:相似算法无法在不同上下文中复用

策略模式(Strategy Pattern)正是为解决这些问题应运而生。该模式定义一系列算法类,将每个算法封装为独立对象,使得它们可以相互替换。这种模式让算法的变化独立于使用它的客户端。

模式结构解析

UML类图核心组成

┌─────────────┐       ┌──────────────────┐
│  Context    │       │<<Strategy>>      │
├─────────────┤       ├──────────────────┤
│- strategy   │<>---->│+ execute()       │
├─────────────┤       └──────────────────┘
│+ setStrategy()          ▲
│+ executeStrategy()      │
└─────────────┘      ┌─────┴─────┐
                ┌────┴────┐ ┌────┴────┐
                │ConcreteA│ │ConcreteB│
                └─────────┘ └─────────┘

核心角色

  1. Strategy(抽象策略):定义算法族的公共接口
  2. ConcreteStrategy(具体策略):实现具体算法的类
  3. Context(环境):持有一个策略引用,负责策略的切换和执行

Java实现示例:电商折扣系统

// 策略接口
interface DiscountStrategy {
    double applyDiscount(double price);
}

// 具体策略类
class NoDiscount implements DiscountStrategy {
    @Override
    public double applyDiscount(double price) {
        return price;
    }
}

class BlackFridayDiscount implements DiscountStrategy {
    @Override
    public double applyDiscount(double price) {
        return price * 0.5;
    }
}

class MembershipDiscount implements DiscountStrategy {
    @Override
    public double applyDiscount(double price) {
        return price * 0.8;
    }
}

// 上下文类
class ShoppingCart {
    private DiscountStrategy strategy;
    private double totalAmount;

    public void setStrategy(DiscountStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void addItem(double price) {
        totalAmount += price;
    }
    
    public double checkout() {
        return strategy != null ? 
               strategy.applyDiscount(totalAmount) : 
               totalAmount;
    }

}

// 使用示例
public class Main {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        cart.addItem(100);
        cart.addItem(200);

        // 动态切换策略
        cart.setStrategy(new BlackFridayDiscount());
        System.out.println("Black Friday Price: " + cart.checkout());
    
        cart.setStrategy(new MembershipDiscount());
        System.out.println("Member Price: " + cart.checkout());
    }

}

实现特点

  • 使用接口定义策略规范
  • 上下文类持有策略引用
  • 完全遵循开闭原则,新增策略无需修改已有代码
  • 通过多态实现运行时策略切换

Go实现示例:文件压缩工具

package main

import "fmt"

// 策略接口
type CompressionStrategy interface {
    Compress(file string) string
}

// 具体策略
type ZipStrategy struct{}

func (z *ZipStrategy) Compress(file string) string {
    return fmt.Sprintf("%s.zip", file)
}

type RarStrategy struct{}

func (r *RarStrategy) Compress(file string) string {
    return fmt.Sprintf("%s.rar", file)
}

// 上下文对象
type Compressor struct {
    strategy CompressionStrategy
}

func (c *Compressor) SetStrategy(s CompressionStrategy) {
    c.strategy = s
}

func (c *Compressor) Execute(file string) string {
    if c.strategy == nil {
        return file
    }
    return c.strategy.Compress(file)
}

// 使用示例
func main() {
    compressor := Compressor{}
    

    file := "data.log"
    
    compressor.SetStrategy(&ZipStrategy{})
    fmt.Println("ZIP Result:", compressor.Execute(file))
    
    compressor.SetStrategy(&RarStrategy{})
    fmt.Println("RAR Result:", compressor.Execute(file))

}

Go实现特点

  • 使用隐式接口(无需显式声明实现)
  • 结构体组合替代类继承
  • 更简洁的类型系统
  • 支持接口的零值(nil)处理
  • 符合Go的"接受接口,返回结构体"原则

模式对比分析

特性Java实现Go实现
接口定义显式声明implements隐式实现
空策略处理需要null检查利用nil特性
多态实现类继承体系接口组合
典型应用场景复杂业务规则轻量级策略替换
扩展方式新增实现类新增结构体类型
性能考量虚方法表查找接口值动态分发

应用场景与最佳实践

典型适用场景

  1. 支付网关(不同支付方式处理)
  2. 数据验证(多种校验规则)
  3. 导航系统(不同路径规划算法)
  4. 日志处理(控制台/文件/网络输出)
  5. 游戏开发(AI行为策略切换)

使用原则

  1. 策略对象应为无状态工具类
  2. 避免策略类之间的依赖关系
  3. 策略参数应通过上下文传递
  4. 配合工厂模式管理策略创建
  5. 优先组合而非继承实现扩展

性能优化技巧

  • 对象池管理策略实例(Java)
  • 使用枚举策略(固定策略集合)
  • 策略缓存(Go的sync.Pool)
  • 避免在循环中频繁创建策略对象

模式优缺点剖析

优势

  1. 符合单一职责原则
  2. 提升代码可测试性
  3. 运行时灵活切换算法
  4. 消除复杂的条件判断
  5. 提高代码复用程度

局限性

  1. 可能增加对象数量
  2. 客户端必须了解不同策略
  3. 策略间通信成本较高
  4. 简单场景可能过度设计

扩展与变体

  1. 策略+工厂模式:自动创建策略对象

  2. 策略+模板方法:实现算法骨架复用

  3. 函数式策略(Java8+):

    interface DiscountStrategy {
        double apply(double price);
    }
    
    DiscountStrategy strategy = price -> price * 0.9;
    
  4. Go的函数选项模式

    type Option func(*Config)
    
    func WithStrategy(s Strategy) Option {
        return func(c *Config) {
            c.strategy = s
        }
    }
    

经典应用案例

  1. Java集合框架的Comparator
  2. Go的sort.Sort接口实现
  3. Spring的ResourceLoader策略
  4. Gin框架的路由匹配策略
  5. AWS SDK的RetryPolicy

总结

策略模式通过将算法封装为独立对象,实现了逻辑解耦和灵活扩展。在Java中,它体现着经典的OOP设计思想;在Go中,则展现出接口组合的简洁之美。随着函数式编程的普及,策略模式正在与Lambda表达式、闭包等特性深度融合,展现出更强大的生命力。

在微服务架构和云原生时代,策略模式的应用场景进一步扩展:在服务网格中动态选择路由策略、在配置中心实现灰度发布策略、在Serverless环境中切换冷启动策略等。

欢迎关注公众号:“全栈开发指南针” 这里是技术潮流的风向标,也是你代码旅程的导航仪!🚀 Let’s code and have fun! 🎉