一,策略模式简介
策略模式是一种行为型设计模式,策略模式在软件开发场景中定义了一系列的算法,并将每个算法单独封装在可替换的对象中,使应用程序在运行时可以根据具体的上下文来动态地选择和切换算法,同时保持原有的代码架构不被修改。
策略模式的设计使得算法的实现与调用被分离,让算法可以独立于外部客户端进行开发和改动,使用独立的类来封装特定的算法,也避免了不同算法策略之间的互相影响。
策略模式能适应多种应用场景,为了满足业务需求,应用程序在运行时可以选择不同的算法策略来达到最优的实现效果。
策略模式将不同的算法实现封装成独立的类,使得算法的修改不会影响到客户端代码,提高了应用程序的灵活性和可维护性。策略模式的架构可以避免使用大量的if-else条件语句来大量判断不同的策略分支,优化了代码结构,增加了代码的可扩展性。
策略模式在现实生活中的抽象实例:
交通路线选择:当我们在规划行程路线时,根据行驶时间、行驶距离,可以规划出好几个不同的路线策略。
投资策略选择:投资者在选择投资策略时,可能会考虑不同的策略,比如价值、成长、指数等。
健身计划选择:在健身时,个人根据不同的健身进展选择不同的策略,如有氧运动、力量训练、高强度间歇训练等。
二,策略模式的结构
策略模式主要包含以下组件:
1.策略上下文(Context):
Context类是策略模式的调度核心,其内部包含了一个策略对象,并通过调用具体的策略对象来完成具体操作。Context类对外提供了与客户端交互的API接口,并隐藏了具体的算法细节,Context类相当于一个中间件,将算法封装与客户端调用进行了分离。
2.抽象策略类(Strategy):
Strategy类定义了一个公共接口,该公共接口最终将被具体的算法模块进行实现和重写。
3.具体策略类(ConcreteStrategy):
ConcreteStrategy类实现了Strategy类定义的公共接口,每一个具体策略类都包含特定的算法实现细节,并用来处理特定的应用场景。
组件之间的工作步骤如下:
1.客户端根据业务需要选择一个具体策略类,并初始化一个对应的策略对象。
2.客户端将创建好的策略对象传递给策略上下文。
3.策略上下文调用策略对象的接口函数。
4.当客户端需要更换算法策略时,可以重新选择一个具体策略类,并传递一个新的策略对象给策略上下文。
对应UML类图:
三,策略模式代码样例
Demo1:根据不同的策略,计算出不同的结果
#include <iostream>
class Strategy {
public:
virtual int operation(int input) const = 0;
};
class ConcreteStrategyA : public Strategy {
public:
int operation(int input) const override {
return input * 2;
}
};
class ConcreteStrategyB : public Strategy {
public:
int operation(int input) const override {
return input / 2;
}
};
class Context {
private:
Strategy* strategy;
public:
Context(Strategy* strategy = nullptr) {
this->strategy = strategy;
}
void setStrategy(Strategy* strategy) {
this->strategy = strategy;
}
int execute(int input) const {
return strategy->operation(input);
}
};
int main() {
Context context(new ConcreteStrategyA());
std::cout << "Using Strategy A: " << context.execute(10) << std::endl;
context.setStrategy(new ConcreteStrategyB());
std::cout << "Using Strategy B: " << context.execute(10) << std::endl;
return 0;
}
运行结果:
Using Strategy A: 20
Using Strategy B: 5
Demo2:根据不同的策略,打印不同的提示语
#include <iostream>
class Strategy {
public:
virtual void execute() const = 0;
};
class ConcreteStrategyA : public Strategy {
public:
virtual void execute() const override {
std::cout << "Using Strategy A."
<< std::endl;
}
};
class ConcreteStrategyB : public Strategy {
public:
virtual void execute() const override {
std::cout << "Using Strategy B."
<< std::endl;
}
};
class Context {
private:
Strategy* strategy;
public:
Context(Strategy* strategy) : strategy(strategy) {}
void setStrategy(Strategy* strategy) {
this->strategy = strategy;
}
void executeStrategy() const {
strategy->execute();
}
};
int main() {
Strategy* strategyA = new ConcreteStrategyA();
Strategy* strategyB = new ConcreteStrategyB();
Context context(strategyA);
context.executeStrategy();
context.setStrategy(strategyB);
context.executeStrategy();
delete strategyA;
delete strategyB;
return 0;
}
运行结果:
Using Strategy A.
Using Strategy B.
四,策略模式的应用场景
文件格式处理:代码可以根据不同的文件格式选择不同的解析策略,例如文本格式、XML、JSON等。
交易系统开发:系统根据交易类型、支付渠道等,选择不同的处理策略。
可配置应用开发:用户可以根据不同的业务场景来动态选择不同的配置模板。
通用API开发:当同一个API需要提供多个版本或业务逻辑时,策略模式可以帮助隐藏具体细节。
五,策略模式的优缺点
策略模式的优点:
对“开闭原则”提供完美支持。
基于上下文和算法类的封装,方便管理和调度一系列算法策略。
避免了if-else条件语句的大量使用。
支持灵活的替换算法策略,代码的可读性和扩展性很强。
算法被封装以后,可以独立地被多个客户端和上下文复用。
策略模式的缺点:
使类和对象的数量变得更多,增加了系统的复杂性。
如果策略被划分得过于细化,会导致过度设计,不易于代码理解。
代码涉及多个对象的创建和销毁,性能开销增大,大量使用会引起性能问题。
六,代码实战
Demo:集成冒泡排序算法、选择排序算法给客户端进行调用
#include <iostream>
#include <vector>
class SortingStrategy {
public:
virtual void sort(std::vector<int>& arr) = 0;
};
class BubbleSort: public SortingStrategy {
public:
void sort(std::vector<int>& arr) override
{
std::cout << "\nUse Strategy: BubbleSort."
<< std::endl;
int n = arr.size();
for (size_t i = 0; i < n - 1; ++i) {
for (size_t j = 0; j < n - i - 1; ++j) {
if (arr[j] > arr[j + 1]) {
std::swap(arr[j], arr[j + 1]);
}
}
}
}
};
class SelectionSort: public SortingStrategy {
public:
void sort(std::vector<int>& arr) override
{
std::cout << "\nUse Strategy: SelectionSort."
<< std::endl;
int n = arr.size();
for (int i = 0; i < n - 1; ++i) {
int minIndex = i;
for (int j = i + 1; j < n; ++j) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
std::swap(arr[i], arr[minIndex]);
}
}
};
class SortContext {
private:
SortingStrategy* strategy;
public:
void setStrategy(SortingStrategy* strategy)
{
this->strategy = strategy;
}
void executeStrategy(std::vector<int>& arr)
{
strategy->sort(arr);
}
};
int main()
{
std::vector<int> data = { 23, 5, 6, 36, 25, 4, 20 };
SortContext context;
BubbleSort bubbleSort;
SelectionSort selectSort;
context.setStrategy(&bubbleSort);
context.executeStrategy(data);
for (const auto& num : data) {
std::cout << num << " ";
}
data = { 32, 45, 5, 6, 100, 7 };
context.setStrategy(&selectSort);
context.executeStrategy(data);
for (const auto& num : data) {
std::cout << num << " ";
}
return 0;
}
运行结果:
Use Strategy: BubbleSort.
4 5 6 20 23 25 36
Use Strategy: SelectionSort.
5 6 7 32 45 100
七,参考阅读
https://www.geeksforgeeks.org/strategy-method-design-pattern-c-design-patterns/
https://refactoringguru.cn/design-patterns/strategy
https://design-patterns.readthedocs.io/zh-cn/latest/behavioral_patterns/strategy.html
https://www.vishalchovatiya.com/strategy-design-pattern-in-modern-cpp/