基础介绍
策略模式是一种行为型设计模式,它可以让用户定义一系列算法,并将每个算法独立的封装成独立的类,使他们可以互相替换。这种模式让算法的变化独立与使用算法的前端。策略模式基本的结构如下所示:
//基本抽象类
class Strategy
{
public
virtual void exec()=0; //行为接口
~Strategy()=default; //默认的析构函数
}
//具体抽象类
class ConcreteStrategyA: public Strategy
{
public:
void exec() override
{
std::cout << "come into ConcreteStrategyA"<<std::endl;
}
}
class ConcreteStrategyB: public Strategy
{
public:
void exec() override
{
std::cout << "come into ConcreteStrategyB"<<std::endl;
}
}
//上下文类
class Context
{
private:
std::unique_ptr<Strategy> m_strategy;
public:
//构造函数
Context(std::unique_ptr<PaymentStrategy> ptr):m_strategy(ptr);
//设置策略接口
void setStrategy(std::unique_ptr<PaymentStrategy> strategy)
{
m_strategy = std::move(strategy);
}
//执行算法切换的接口,这里可以有多种形式,可以带参数也可以不带参数
void execute()
{
if(m_strategy)
{
m_strategy->exec();
}
else
{
...
}
}
}
总结上面的代码可以发现,策略模式的核心组件:
- 策略抽象类:该类提供了基本的接口,子类需要实现该接口。
- 具体的策略类:这些类实现了抽象了接口,这些类的封装实现了不同的行为模式或不同的算法。
- 上下文类:这个类可以理解为客户端类,用户通过这个类来实现不同的策略的切换。
实现原理
策略模式的实现采用的是组合方式实现的,组合在c++关系可以便是为has-a;关联在c++中的关系可以表述为Use-a。他们的区别就是生命周期,在组合关系下,当context销毁时,包含的对象也会被销毁,这哩是通过std::unique_ptr来实现的;而在关联关系下,包含的对象的生命周期不由context决定,而是由外部逻辑独立控制。请看下面的示例:
//组合方式实现的策略模式
class Context {
private:
// 组合方式:Context 包含一个 Strategy 对象
std::unique_ptr<Strategy> strategy; // 生命周期由 Context 管理
public:
Context(std::unique_ptr<Strategy> s) : strategy(std::move(s)) {}
void executeStrategy() {
if(strategy) {
strategy->execute();
}
}
};
//关联方式实现的策略模式
class Context {
private:
// 关联方式:Context 持有 Strategy 的引用或指针
Strategy* strategy; // 生命周期由外部管理
public:
Context(Strategy* s) : strategy(s) {}
void setStrategy(Strategy* s) {
strategy = s;
}
void executeStrategy() {
if(strategy) {
strategy->execute();
}
}
};
组合方式实现的策略模式特点
- Strategy 对象的生命周期由 Context 管理
- Strategy 对象是 Context 的一部分
强耦合关系,Strategy 不能被其他对象共享
- 更符合"Has-A"关系
关联方式实现的策略模式特点
- Strategy 对象的生命周期由外部管理
- Strategy 对象可以被多个 Context 共享
- 松耦合关系
- 更符合"Uses-A"关系
应用场景
策略模式常用的应用场景包括以下几个方面:
- 支付场景,不同的支付比如,信用卡支持/钱包支付,又比如正常支付/节假日促销支付等。
- 排序场景,不同的排序算法,用户需要根据客户的需求提供不通过的排序算法进行排序。
- 压缩算法场景,提供不同的压缩算法。
- 日志记录类场景,提供不同的日志记录策略,例如数据库、文件、显示终端等
请注意,不同的策略之间的是平等的,没有主次之分。
如果用户遇到需要在运行时根据不同的需求需要使用不同的策略进行处理的时,应当优先考虑策略模式。
具体实例
支付策略
#include <memory>
#include <string>
// 策略接口
class PaymentStrategy {
public:
virtual ~PaymentStrategy() = default;
virtual void pay(double amount) = 0;
};
// 具体策略:信用卡支付
class CreditCardPayment : public PaymentStrategy {
public:
void pay(double amount) override {
std::cout << "Paid " << amount << " using Credit Card" << std::endl;
}
};
// 具体策略:PayPal支付
class PayPalPayment : public PaymentStrategy {
public:
void pay(double amount) override {
std::cout << "Paid " << amount << " using PayPal" << std::endl;
}
};
// 上下文类
class ShoppingCart {
private:
std::unique_ptr<PaymentStrategy> paymentStrategy;
double total;
public:
ShoppingCart() : total(0) {}
void setPaymentStrategy(std::unique_ptr<PaymentStrategy> strategy) {
paymentStrategy = std::move(strategy);
}
void addItem(double price) {
total += price;
}
void checkout() {
if (paymentStrategy) {
paymentStrategy->pay(total);
} else {
std::cout << "No payment strategy set!" << std::endl;
}
}
};
int main() {
ShoppingCart cart;
// 添加商品
cart.addItem(100);
cart.addItem(50);
// 使用信用卡支付
cart.setPaymentStrategy(std::make_unique<CreditCardPayment>());
cart.checkout();
// 使用PayPal支付
cart.setPaymentStrategy(std::make_unique<PayPalPayment>());
cart.checkout();
return 0;
}
排序策略
#include <vector>
#include <algorithm>
// 排序策略接口
template<typename T>
class SortStrategy {
public:
virtual ~SortStrategy() = default;
virtual void sort(std::vector<T>& data) = 0;
};
// 具体策略:快速排序
template<typename T>
class QuickSort : public SortStrategy<T> {
public:
void sort(std::vector<T>& data) override {
std::sort(data.begin(), data.end());
std::cout << "QuickSort used" << std::endl;
}
};
// 具体策略:冒泡排序
template<typename T>
class BubbleSort : public SortStrategy<T> {
public:
void sort(std::vector<T>& data) override {
bool swapped;
for (size_t i = 0; i < data.size() - 1; i++) {
swapped = false;
for (size_t j = 0; j < data.size() - i - 1; j++) {
if (data[j] > data[j + 1]) {
std::swap(data[j], data[j + 1]);
swapped = true;
}
}
if (!swapped) break;
}
std::cout << "BubbleSort used" << std::endl;
}
};
// 排序上下文
template<typename T>
class Sorter {
private:
std::unique_ptr<SortStrategy<T>> strategy;
public:
void setStrategy(std::unique_ptr<SortStrategy<T>> newStrategy) {
strategy = std::move(newStrategy);
}
void sort(std::vector<T>& data) {
if (strategy) {
strategy->sort(data);
}
}
};
int main() {
std::vector<int> numbers = {64, 34, 25, 12, 22, 11, 90};
Sorter<int> sorter;
// 使用冒泡排序
sorter.setStrategy(std::make_unique<BubbleSort<int>>());
sorter.sort(numbers);
// 使用快速排序
numbers = {64, 34, 25, 12, 22, 11, 90}; // 重置数据
sorter.setStrategy(std::make_unique<QuickSort<int>>());
sorter.sort(numbers);
return 0;
}
现代c++策略模式实现之std::funtion
#include <functional>
class ModernShoppingCart {
private:
std::function<void(double)> paymentStrategy;
double total;
public:
ModernShoppingCart() : total(0) {}
void setPaymentStrategy(std::function<void(double)> strategy) {
paymentStrategy = strategy;
}
void addItem(double price) {
total += price;
}
void checkout() {
if (paymentStrategy) {
paymentStrategy(total);
}
}
};
// 使用示例
int main() {
ModernShoppingCart cart;
cart.addItem(100);
// 使用 lambda 表达式定义策略
cart.setPaymentStrategy([](double amount) {
std::cout << "Paid " << amount << " using lambda strategy" << std::endl;
});
cart.checkout();
return 0;
}
为什么使用策略模式?
如果我们不使用策略模式,那么就需要使用很多的if-else来实现类似的功能,那么当添加一种新的策略时,就需要修改已有的代码。这就违反了c++对修改关闭,对扩展开发的原则。该怎么理解对修改关闭,对扩展开放原则呢?简单理解就是不修改已有的代码,但是可以新增代码或者通过读取配置文件的方式实现,不是不能改动代码,而是不能改动已有的代码。请看下面的例子:
//使用if-esle实现的类似策略模式的功能
class Calculator {
public:
double calculate(string operation, double a, double b) {
if (operation == "add") {
return a + b;
} else if (operation == "subtract") {
return a - b;
} else if (operation == "multiply") {
return a * b;
} else if (operation == "divide") {
return a / b;
}
throw std::invalid_argument("Unknown operation");
}
};
//使用策略模式改进的实现
// 策略接口
class OperationStrategy {
public:
virtual double execute(double a, double b) = 0;
virtual ~OperationStrategy() = default;
};
// 具体策略
class AddOperation : public OperationStrategy {
public:
double execute(double a, double b) override { return a + b; }
};
class SubtractOperation : public OperationStrategy {
public:
double execute(double a, double b) override { return a - b; }
};
// 上下文
class Calculator {
private:
std::unique_ptr<OperationStrategy> strategy;
public:
void setStrategy(std::unique_ptr<OperationStrategy> newStrategy) {
strategy = std::move(newStrategy);
}
double calculate(double a, double b) {
return strategy->execute(a, b);
}
};
通过上面的代码对比可以看到:当新增的一种新的策略时,使用策略模式是不需要修改原有的代码的。
注意事项
策略模式通常与工厂模式组合使用。