89、STL与策略模式:算法封装、策略切换、运行时选择

策略模式,说白了就是「把算法装进盒子里,想用哪个拿哪个」。

我在项目中遇到过不少这样的场景:同一个功能,在不同环境下要用不同的算法。比如数据压缩,网络好的时候用高压缩比算法,网络差的时候用快速算法。如果把这些算法硬编码到业务逻辑里,代码会变得又臭又长。

STL 本身没有直接提供策略模式,但它的函数对象、std::function、以及各种算法接口,天然就是为策略模式准备的。你想想看,std::sort 的第三个参数不就是个策略吗?

策略模式的核心思想

策略模式解决的是「如何优雅地切换算法」这个问题。它把算法封装成独立的策略类,然后在运行时选择使用哪个策略。

我个人的习惯是:先定义策略接口,再实现具体策略,最后通过一个上下文类来管理策略的切换。这样,新增一个策略只需要写一个新类,不用改现有代码。

策略模式的三个角色:

  • 策略接口:定义算法的统一接口
  • 具体策略:实现具体的算法
  • 上下文:持有策略对象,负责调用策略

STL 中的策略模式体现

其实 STL 里到处都是策略模式的影子。你看 std::sortstd::find_ifstd::accumulate,它们都接受一个可调用对象作为参数。这个可调用对象就是策略。

STL 组件 策略参数 典型用途
std::sort 比较函数/函数对象 升序、降序、自定义排序
std::find_if 谓词函数 按条件查找元素
std::accumulate 二元操作函数 求和、求积、自定义归约
std::transform 一元/二元操作函数 数据转换、映射

嗯,这里要注意:STL 的策略是编译期绑定的,而传统策略模式是运行时绑定的。但通过 std::function 和虚函数,我们可以轻松实现运行时策略切换。

实战:数据压缩策略

我记得有一次做日志系统,需要根据当前系统负载选择不同的压缩算法。负载低的时候用高压缩比算法,负载高的时候用快速算法。这就是典型的策略模式应用场景。

#include <iostream>
#include <functional>
#include <string>
#include <vector>

// 策略接口:压缩算法
class CompressionStrategy {
public:
    virtual ~CompressionStrategy() = default;
    virtual std::string compress(const std::string& data) = 0;
    virtual std::string name() = 0;
};

// 具体策略:快速压缩
class FastCompression : public CompressionStrategy {
public:
    std::string compress(const std::string& data) override {
        // 简单的快速压缩模拟
        return "[Fast] " + data.substr(0, data.size() / 2);
    }
    std::string name() override { return "FastCompression"; }
};

// 具体策略:高压缩比
class HighCompression : public CompressionStrategy {
public:
    std::string compress(const std::string& data) override {
        // 高压缩比算法模拟
        return "[High] " + data.substr(0, data.size() / 4);
    }
    std::string name() override { return "HighCompression"; }
};

// 上下文:压缩器
class Compressor {
private:
    std::unique_ptr<CompressionStrategy> strategy_;
public:
    void setStrategy(std::unique_ptr<CompressionStrategy> strategy) {
        strategy_ = std::move(strategy);
    }
    
    std::string compress(const std::string& data) {
        if (!strategy_) {
            throw std::runtime_error("No strategy set!");
        }
        std::cout << "Using strategy: " << strategy_->name() << std::endl;
        return strategy_->compress(data);
    }
};

int main() {
    Compressor compressor;
    std::string data = "Hello, this is a long string that needs compression!";
    
    // 运行时切换策略
    compressor.setStrategy(std::make_unique<FastCompression>());
    auto result1 = compressor.compress(data);
    std::cout << "Result: " << result1 << std::endl;
    
    compressor.setStrategy(std::make_unique<HighCompression>());
    auto result2 = compressor.compress(data);
    std::cout << "Result: " << result2 << std::endl;
    
    return 0;
}

用 std::function 实现轻量策略

如果策略比较简单,没必要定义一堆类。用 std::function 加 lambda 表达式,代码会更简洁。我个人比较喜欢这种方式,尤其是策略数量不多的时候。

#include <iostream>
#include <functional>
#include <string>
#include <map>

class LightweightCompressor {
private:
    std::function<std::string(const std::string&)> strategy_;
    std::string strategy_name_;
public:
    void setStrategy(std::function<std::string(const std::string&)> strategy,
                     const std::string& name) {
        strategy_ = std::move(strategy);
        strategy_name_ = name;
    }
    
    std::string compress(const std::string& data) {
        if (!strategy_) {
            throw std::runtime_error("No strategy set!");
        }
        std::cout << "Using strategy: " << strategy_name_ << std::endl;
        return strategy_(data);
    }
};

int main() {
    LightweightCompressor compressor;
    
    // 直接传入 lambda 作为策略
    compressor.setStrategy(
        [](const std::string& data) {
            return "[Fast] " + data.substr(0, data.size() / 2);
        },
        "FastCompression"
    );
    
    auto result = compressor.compress("Hello World!");
    std::cout << result << std::endl;
    
    // 切换策略
    compressor.setStrategy(
        [](const std::string& data) {
            return "[High] " + data.substr(0, data.size() / 4);
        },
        "HighCompression"
    );
    
    result = compressor.compress("Hello World!");
    std::cout << result << std::endl;
    
    return 0;
}

我的经验:如果策略有状态(比如需要缓存、计数器),用类实现更合适。如果策略只是纯函数,用 std::function 加 lambda 就够了。

策略模式与 STL 算法的结合

STL 算法本身就是策略模式的绝佳搭档。你可以把策略对象传给算法,实现高度灵活的数据处理。

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

// 策略:排序方式
enum class SortStrategy { Ascending, Descending, Custom };

class Sorter {
private:
    std::function<bool(int, int)> comparator_;
public:
    void setStrategy(SortStrategy strategy) {
        switch (strategy) {
            case SortStrategy::Ascending:
                comparator_ = std::less<int>();
                break;
            case SortStrategy::Descending:
                comparator_ = std::greater<int>();
                break;
            case SortStrategy::Custom:
                // 自定义策略,比如按绝对值排序
                comparator_ = [](int a, int b) {
                    return std::abs(a) < std::abs(b);
                };
                break;
        }
    }
    
    void sort(std::vector<int>& data) {
        std::sort(data.begin(), data.end(), comparator_);
    }
};

int main() {
    std::vector<int> data = {3, -1, 4, -5, 2, -6};
    
    Sorter sorter;
    
    sorter.setStrategy(SortStrategy::Ascending);
    sorter.sort(data);
    // data: -6, -5, -1, 2, 3, 4
    
    sorter.setStrategy(SortStrategy::Custom);
    sorter.sort(data);
    // data: -1, 2, 3, 4, -5, -6 (按绝对值排序)
    
    return 0;
}

策略模式的核心流程图

策略模式核心流程 上下文 (Context) 策略接口 (Strategy) 具体策略A 具体策略B 具体策略C 运行时选择:根据条件动态切换具体策略

避坑指南

我曾经踩过的坑:

  • 策略对象生命周期管理:如果上下文持有策略的指针,一定要确保策略对象在上下文使用期间有效。我建议用 std::unique_ptrstd::shared_ptr 管理。
  • 策略切换的线程安全:如果在多线程环境中切换策略,记得加锁。或者用不可变策略对象,每次切换时创建新对象。
  • 不要过度设计:如果只有两三种策略,而且不太可能扩展,直接用 if-elseswitch 反而更清晰。策略模式适合「策略经常变化」或「策略数量较多」的场景。

什么时候用策略模式?

说白了,当你发现代码里有一堆 if-elseswitch 来选择算法时,就该考虑策略模式了。但也要注意,不是所有 if-else 都需要重构。

场景 推荐做法 原因
算法固定,很少变化 直接写函数 简单直接,不需要额外抽象
算法可能扩展 策略模式 开闭原则,新增策略不影响现有代码
算法需要运行时切换 策略模式 + std::function 灵活,支持动态绑定
算法有状态 类实现的策略模式 方便管理状态和生命周期

我的建议:刚开始写代码时,先用简单的函数或 lambda 实现。当发现「又要加一种算法」时,再重构为策略模式。过早优化是万恶之源,这句话在策略模式上同样适用。

策略模式加上 STL,就像给算法装上了快拆接口。你可以随时换算法,而调用方完全不用关心具体实现。这种解耦能力,在大型项目中尤其珍贵。


公众号:蓝海资料掘金营,微信deep3321