C++设计模式之Strategy策略模式
模式定义
策略模式:定义一系列算法,把她们一个个封装起来,并且使它们可以相互替换(变化)。该模式使得算法可以独立于使用它的客户程序(稳定)而变化(扩展,子类化)。
需要区分策略模式和之前提到的模板方法模式的不同,模板方法是通过继承来改变部分步骤,而策略模式是通过组合不同的策略对象来改变整体行为。
核心思想
- 解耦算法与客户端:将算法逻辑从主类中分离,避免代码臃肿。
- 运行时动态切换:通过更换策略对象,灵活改变程序行为。
- 开闭原则:新增算法无需修改现有代码,只需添加新策略类。
动机(Motivation)
- 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。
- 如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
结构(Structure)
实现步骤
1. 定义策略接口(基于继承)
startegy.h
#pragma once
class Strategy {
//策略模式的运算法则
public:
virtual ~Strategy() {}
virtual void doSomething() = 0;
};
2.实现具体策略
concretestrategy.h
#pragma once
#include<iostream>
#include"strategy.h"
class ConcreteStrategy1 :public Strategy {
public:
void doSomething()override {
std::cout << "ConcreteStrategy1 doSomething" << std::endl;
}
};
class ConcreteStrategy2 :public Strategy {
public:
void doSomething()override {
std::cout << "ConcreteStrategy2 doSomething" << std::endl;
}
};
3.上下文类(Context)
持有策略对象的引用,并委托算法执行
context.h
#pragma once
#include"strategy.h"
class Context {
public:
//构造函数设置具体策略
Context(Strategy *strategy) {
strategy_ = strategy;
}
//封装后的策略方法
void doAnything() {
strategy_->doSomething();
}
private:
Strategy *strategy_ = nullptr;
};
4. 在main中调用
#include "concretestrategy.h"
#include "context.h"
int main()
{
Strategy *strategy = new ConcreteStrategy1();
//声明上下文对象
Context context(strategy);
context.doAnything();
delete strategy;
}
应用场景(基于继承)
传统实现(基于继承),实现快速排序算法,归并排序算法的策略模式
1.定义策略接口
sortingstrategy.h
#pragma once
#include<iostream>
#include<vector>
//策略接口:定义算法的抽象操作
class SortingStrategy {
public:
virtual ~SortingStrategy() = default;
virtual void sort(std::vector<int>&data)const = 0;
};
2.实现具体策略
#pragma once
#include"sortingstrategy.h"
//具体策略1:快速排序
class QuickSort :public SortingStrategy
{
public:
void sort(std::vector<int>&data)const override {
std::cout << "Sorting with QuickSort\n";
//快速排序具体实现
}
};
class MergeSort :public SortingStrategy
{
public:
void sort(std::vector<int>&data)const override {
std::cout << "Sorting with MergeSort\n";
//归并排序具体实现
}
};
3.上下文类(Context)
持有策略对象的引用,并委托算法执行
context.h
#pragma once
#include"concretesorting.h"
class Sorter {
private:
std::unique_ptr<SortingStrategy> strategy_; //使用智能指针管理策略对象
public:
explicit Sorter(std::unique_ptr<SortingStrategy> strategy)
:strategy_(std::move(strategy)){}
//动态切换策略
void setStrategy(std::unique_ptr<SortingStrategy>strategy) {
strategy_ = std::move(strategy);
}
//执行排序
void executeSort(std::vector<int>&data)const {
if (strategy_) {
strategy_->sort(data);
}
}
};
4.在main中调用测试
#include"context.h"
#include<vector>
int main()
{
std::vector<int> data = { 5,2,7,1,9 };
Sorter sorter(std::make_unique<QuickSort>());
sorter.executeSort(data); //输出:Sorting with QuickSort
sorter.setStrategy(std::make_unique<MergeSort>());
sorter.executeSort(data); //输出Sorting with MergeSort
return 0;
}
现代 C++ 优化(基于 std::function)
1.使用函数对象替代接口
无需继承策略接口,直接通过std::function封装算法:
context.h
#pragma once
#include<functional>
#include<vector>
class Sorter {
public:
using Strategy = std::function<void(std::vector<int>&)>;
explicit Sorter(Strategy strategy):strategy_(std::move(strategy)){}
void setStrategy(Strategy strategy) {
strategy_ = std::move(strategy);
}
void executeSort(std::vector<int>&data)const {
if (strategy_) {
strategy_(data);
}
}
private:
Strategy strategy_;
};
2.定义策略函数
context.h
void quickSort(std::vector<int>& data) {
std::cout << "Sorting with QuickSort\n";
// 快速排序实现...
}
void mergeSort(std::vector<int>& data) {
std::cout << "Sorting with MergeSort\n";
// 归并排序实现...
}
// 支持 Lambda 表达式
auto bubbleSort = [](std::vector<int>& data) {
std::cout << "Sorting with BubbleSort\n";
// 冒泡排序实现...
};
3.main.cpp中使用示例
#include"context.h"
int main()
{
std::vector<int> data = { 5,2,7,1,9 };
Sorter sorter(quickSort);
sorter.executeSort(data); //输出Sorting with QuickSort
sorter.setStrategy(mergeSort); //输出Sorting with MergeSort
sorter.executeSort(data);
sorter.setStrategy(bubbleSort);//输出Sorting with BubbleSort
sorter.executeSort(data);
//直接传达Lambda
sorter.setStrategy([](std::vector<int>&data) {
std::cout << "Custom Lambda Strategy \n";
});
sorter.executeSort(data);
return 0;
}
优化方向
1.编译时策略(模板实现)
通过模板参数在编译时绑定 策略,消除运行时开销:
#pragma once
#include<vector>
#include<iostream>
template<typename Strategy>
class ComplieTimeSorter {
public:
void executeSort(std::vector<int>&data)const {
Strategy::sort(data); //策略需要提供静态方法
}
};
//策略类无需继承接口
struct QuickSort {
static void sort(std::vector<int>&data) {
std::cout << "Compile-Time QuickSort \n";
}
};
int main()
{
std::vector<int> data = { 5,2,7,1,9 };
ComplieTimeSorter<QuickSort>sorter;
sorter.executeSort(data); //输出Complie-Time QuickSort
system("pause");
return 0;
}
2.线程安全策略切换
在多线程环境中安全且策略:
#pragma once
#include<mutex>
#include<vector>
class ThreadSafeSorter {
public:
using Strategy = std::function<void(std::vector<int>&)>;
void setStrategy(Strategy strategy) {
std::lock_guard<std::mutex>lock(mtx_);
strategy_ = std::move(strategy);
}
void executeSort(std::vector<int>&data)const {
std::lock_guard<std::mutex>lock(mtx_);
if (strategy_) {
strategy_(data);
}
}
private:
mutable std::mutex mtx_;
Strategy strategy_;
};
要点总结
- Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。
- Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式。
- 如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。