云计算百科
云计算领域专业知识百科平台

策略模式——C++

一、什么是策略模式?

策略模式(Strategy Pattern) 的核心思想非常简单:

把一组可以互相替换的算法/行为封装起来,让程序在运行时可以自由切换。

也就是说:

原来的代码:

if (条件A)
算法A
else if (条件B)
算法B
else if (条件C)
算法C

策略模式改成:

Context
|
v
Strategy接口
/ | \\
/ | \\
策略A 策略B 策略C

Context 不关心具体怎么做,只需要调用:

strategy->execute();

二、最简单的 C++ 例子

比如我们做一个计算器,有三种计算方式:

加法
减法
乘法

  • 定义策略接口
  • class Strategy
    {
    public:
    virtual ~Strategy() = default;

    virtual int calculate(int a, int b) = 0;
    };

    这里的 Strategy 就是策略基类。

    它规定:

    所有策略都必须实现 calculate()。

  • 加法策略
  • class AddStrategy : public Strategy
    {
    public:
    int calculate(int a, int b) override
    {
    return a + b;
    }
    };

  • 减法策略
  • class SubStrategy : public Strategy
    {
    public:
    int calculate(int a, int b) override
    {
    return a b;
    }
    };

  • 乘法策略
  • class MulStrategy : public Strategy
    {
    public:
    int calculate(int a, int b) override
    {
    return a * b;
    }
    };

    三、Context:使用策略

    我们再定义一个 Calculator:

    class Calculator
    {
    public:

    void setStrategy(std::unique_ptr<Strategy> strategy)
    {
    m_strategy = std::move(strategy);
    }

    int calculate(int a, int b)
    {
    if (!m_strategy)
    return 0;

    return m_strategy->calculate(a, b);
    }

    private:

    std::unique_ptr<Strategy> m_strategy;
    };

    这里的 Calculator 就是 Context(上下文)。

    它不关心:

    a + b

    还是:

    a – b

    还是:

    a * b

    它只知道:

    m_strategy->calculate(a, b);

    四、使用

    #include <iostream>
    #include <memory>

    int main()
    {
    Calculator calculator;

    // 使用加法策略
    calculator.setStrategy(
    std::make_unique<AddStrategy>()
    );

    std::cout << calculator.calculate(10, 5) << std::endl;
    // 15

    // 切换成减法策略
    calculator.setStrategy(
    std::make_unique<SubStrategy>()
    );

    std::cout << calculator.calculate(10, 5) << std::endl;
    // 5

    // 切换成乘法策略
    calculator.setStrategy(
    std::make_unique<MulStrategy>()
    );

    std::cout << calculator.calculate(10, 5) << std::endl;
    // 50

    return 0;
    }

    这里最重要的地方就是:

    calculator.setStrategy(
    std::make_unique()
    );

    然后:

    calculator.setStrategy(
    std::make_unique()
    );

    运行过程中直接更换算法。

    这就是策略模式最核心的地方。

    五、如果不用策略模式会怎样?

    例如你写:

    class Calculator
    {
    public:

    int calculate(int a, int b, char op)
    {
    if (op == '+')
    {
    return a + b;
    }
    else if (op == '-')
    {
    return a b;
    }
    else if (op == '*')
    {
    return a * b;
    }
    else if (op == '/')
    {
    return a / b;
    }

    return 0;
    }
    };

    刚开始没问题。

    但是以后增加:

    %
    幂运算
    平方根
    平均值
    最大值
    最小值

    就会变成:

    if (...)
    {
    }
    else if (...)
    {
    }
    else if (...)
    {
    }
    else if (...)
    {
    }
    else if (...)
    {
    }
    else if (...)
    {
    }

    Calculator 会越来越复杂。

    六、策略模式解决了什么?

    采用策略模式:

    class Strategy
    {
    public:
    virtual ~Strategy() = default;

    virtual int calculate(int a, int b) = 0;
    };

    然后:

    Strategy
    |
    ┌──────────┼──────────┐
    ↓ ↓ ↓
    AddStrategy SubStrategy MulStrategy

    以后增加除法:

    class DivStrategy : public Strategy
    {
    public:
    int calculate(int a, int b) override
    {
    return a / b;
    }
    };

    Calculator 完全不需要修改。

    这就是策略模式非常重要的一个优点:

    增加策略时,不需要修改 Context。

    七、结合 Qt/C++ 项目理解

    做工业软件、设备通信、图像处理时,策略模式其实非常常见。

    例如你有不同的图像处理算法:

    图像处理
    |
    ├── 二值化
    ├── 灰度化
    ├── 归一化
    ├── 伪彩色
    └── 滤波

    可以设计成:

    class ImageProcessStrategy
    {
    public:
    virtual ~ImageProcessStrategy() = default;

    virtual cv::Mat process(const cv::Mat& src) = 0;
    };

    二值化策略

    class ThresholdStrategy : public ImageProcessStrategy
    {
    public:

    cv::Mat process(const cv::Mat& src) override
    {
    cv::Mat dst;

    cv::threshold(
    src,
    dst,
    128,
    255,
    cv::THRESH_BINARY
    );

    return dst;
    }

    };
    归一化策略

    class NormalizeStrategy : public ImageProcessStrategy
    {
    public:

    cv::Mat process(const cv::Mat& src) override
    {
    cv::Mat dst;

    cv::normalize(
    src,
    dst,
    0,
    255,
    cv::NORM_MINMAX
    );

    return dst;
    }
    };

    ImageProcessor

    class ImageProcessor
    {
    public:

    void setStrategy(
    std::unique_ptr<ImageProcessStrategy> strategy)
    {
    m_strategy = std::move(strategy);
    }

    cv::Mat process(const cv::Mat& src)
    {
    if (!m_strategy)
    return cv::Mat();

    return m_strategy->process(src);
    }

    private:

    std::unique_ptr<ImageProcessStrategy> m_strategy;
    };

    使用:

    ImageProcessor processor;

    // 使用二值化
    processor.setStrategy(
    std::make_unique<ThresholdStrategy>()
    );

    cv::Mat dst = processor.process(src);

    // 然后需要归一化:

    processor.setStrategy(
    std::make_unique<NormalizeStrategy>()
    );

    dst = processor.process(src);

    八、Qt 项目里更典型的例子

    比如你有不同的通信方式:

    设备通信
    |
    ├── TCP
    ├── UDP
    ├── Serial
    └── Modbus

    可以定义:

    class CommunicationStrategy
    {
    public:
    virtual ~CommunicationStrategy() = default;

    virtual bool connectDevice() = 0;

    virtual bool sendData(
    const QByteArray& data) = 0;

    virtual QByteArray receiveData() = 0;
    };

    TCP:

    class TcpStrategy : public CommunicationStrategy
    {
    public:

    bool connectDevice() override
    {
    qDebug() << "TCP connect";
    return true;
    }

    bool sendData(const QByteArray& data) override
    {
    qDebug() << "TCP send:" << data;
    return true;
    }

    QByteArray receiveData() override
    {
    qDebug() << "TCP receive";
    return {};
    }
    };

    串口:

    class SerialStrategy : public CommunicationStrategy
    {
    public:

    bool connectDevice() override
    {
    qDebug() << "Serial connect";
    return true;
    }

    bool sendData(const QByteArray& data) override
    {
    qDebug() << "Serial send:" << data;
    return true;
    }

    QByteArray receiveData() override
    {
    qDebug() << "Serial receive";
    return {};
    }
    };

    然后:

    class Device
    {
    public:

    void setStrategy(
    std::unique_ptr<CommunicationStrategy> strategy)
    {
    m_strategy = std::move(strategy);
    }

    bool connectDevice()
    {
    return m_strategy &&
    m_strategy->connectDevice();
    }

    bool sendData(const QByteArray& data)
    {
    return m_strategy &&
    m_strategy->sendData(data);
    }

    private:

    std::unique_ptr<CommunicationStrategy> m_strategy;
    };

    使用:

    Device device;
    // TCP
    device.setStrategy(
    std::make_unique<TcpStrategy>()
    );

    device.connectDevice();

    device.sendData("Hello");

    // 如果改成串口:

    device.setStrategy(
    std::make_unique<SerialStrategy>()
    );

    Device 的代码完全不用修改。

    九、策略模式的结构

    你可以记住这三个角色:

    ┌──────────────────────┐
    │ Context │
    │ │
    │ 保存 Strategy │
    │ 调用 Strategy │
    └──────────┬───────────┘


    ┌──────────────────────┐
    │ Strategy │
    │ 策略接口 │
    │ │
    │ execute()
    └──────────┬───────────┘

    ┌────┼────┐
    ↓ ↓ ↓
    StrategyA B C

    对应到 C++:

    class Strategy
    {
    public:
    virtual void execute() = 0;
    };
    class StrategyA : public Strategy
    {
    public:
    void execute() override
    {
    // 算法A
    }
    };
    class StrategyB : public Strategy
    {
    public:
    void execute() override
    {
    // 算法B
    }
    };
    class Context
    {
    private:
    std::unique_ptr<Strategy> strategy;
    };

    十、策略模式和简单工厂的区别

    这个很容易混淆。

    工厂模式

    解决的是:

    “我要创建哪个对象?”

    例如:

    auto strategy = StrategyFactory::create(“tcp”);
    策略模式

    解决的是:

    “我要使用哪个算法/行为?”

    例如:

    device.setStrategy(
    std::make_unique()
    );

    两者实际上可以结合:

    Factory


    创建具体 Strategy


    Context


    执行具体策略

    在 Qt + C++ + 工业设备通信/图像处理项目里,这种组合非常实用。

    一句话记忆:

    策略模式 = 把容易变化的算法抽出来,让它们可以互相替换。

    如果你正在系统学习 GoF 设计模式,那么策略模式可以重点理解成:“把一堆 if/else 中变化的行为,变成一个个独立的类。”

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 策略模式——C++
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!