C++设计模式总结-汇总了全部23种设计模式的详细说明
第22种:策略模式
一、策略模式的基本介绍
1.1 模式定义与核心思想
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列的算法,并将每个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。
简单来说,就是我们有多种解决问题的方法(算法),把这些方法分别封装好,然后在需要的时候可以灵活地选择使用哪一种方法,而不用改变使用这些方法的代码。这就好比我们出行可以选择步行、骑自行车、坐公交车或者打车,不同的出行方式就是不同的策略,我们可以根据实际情况(比如距离远近、时间充裕与否等)来选择合适的策略。
1.2 模式本质与设计原则
策略模式的本质是将算法的定义和使用分离。它遵循了开闭原则(Open - Closed Principle),即对扩展开放,对修改关闭。当我们需要新增一种算法时,只需要创建一个新的策略类,而不需要修改使用这些算法的客户端代码。同时,它也遵循了单一职责原则,每个策略类只负责实现一种具体的算法,职责清晰。
1.3 模式结构与角色
策略模式主要包含以下三个角色:
1.3.1 策略接口(Strategy)
策略接口定义了所有具体策略类必须实现的方法。它是所有具体策略的公共抽象,客户端通过这个接口来调用具体策略的算法。
// 策略接口
class Strategy {
public:
virtual void execute() = 0;
virtual ~Strategy() {}
};
1.3.2 具体策略类(Concrete Strategy)
具体策略类实现了策略接口中定义的方法,每个具体策略类封装了一种具体的算法。
// 具体策略类A
class ConcreteStrategyA : public Strategy {
public:
void execute() override {
std::cout << "Executing strategy A" << std::endl;
}
};
// 具体策略类B
class ConcreteStrategyB : public Strategy {
public:
void execute() override {
std::cout << "Executing strategy B" << std::endl;
}
};
1.3.3 上下文类(Context)
上下文类持有一个策略接口的引用,负责根据客户端的需求选择合适的策略,并调用该策略的方法。
// 上下文类
class Context {
private:
Strategy* strategy;
public:
Context(Strategy* s) : strategy(s) {}
void setStrategy(Strategy* s) {
strategy = s;
}
void executeStrategy() {
if (strategy) {
strategy->execute();
}
}
};
1.4 简单使用示例
下面是一个简单的使用策略模式的示例代码:
#include <iostream>
// 策略接口
class Strategy {
public:
virtual void execute() = 0;
virtual ~Strategy() {}
};
// 具体策略类A
class ConcreteStrategyA : public Strategy {
public:
void execute() override {
std::cout << "Executing strategy A" << std::endl;
}
};
// 具体策略类B
class ConcreteStrategyB : public Strategy {
public:
void execute() override {
std::cout << "Executing strategy B" << std::endl;
}
};
// 上下文类
class Context {
private:
Strategy* strategy;
public:
Context(Strategy* s) : strategy(s) {}
void setStrategy(Strategy* s) {
strategy = s;
}
void executeStrategy() {
if (strategy) {
strategy->execute();
}
}
};
int main() {
ConcreteStrategyA strategyA;
ConcreteStrategyB strategyB;
Context context(&strategyA);
context.executeStrategy();
context.setStrategy(&strategyB);
context.executeStrategy();
return 0;
}
在这个示例中,我们首先定义了策略接口 Strategy,然后实现了两个具体策略类 ConcreteStrategyA 和 ConcreteStrategyB。接着创建了上下文类 Context,它持有一个 Strategy 指针。在 main 函数中,我们先创建了两个具体策略的对象,然后将 ConcreteStrategyA 传递给 Context 对象,调用 executeStrategy 方法执行策略 A。之后,我们通过 setStrategy 方法将策略切换为 ConcreteStrategyB,再次调用 executeStrategy 方法执行策略 B。
二、策略模式的内部原理
2.1 封装与委托机制
策略模式的核心在于封装和委托。封装体现在每个具体策略类将自己的算法实现封装在内部,对外只提供一个统一的接口。这样,客户端不需要关心具体策略的实现细节,只需要通过策略接口来调用相应的方法。
委托机制则体现在上下文类中。上下文类持有一个策略接口的引用,它将具体的算法执行委托给了具体的策略类。当客户端调用上下文类的 executeStrategy 方法时,上下文类只是简单地调用其所持有的策略对象的 execute 方法,从而实现了算法的执行。
2.2 多态的运用
策略模式充分利用了 C++ 的多态特性。策略接口定义了一个纯虚函数 execute,具体策略类通过重写这个函数来实现自己的算法。上下文类持有一个策略接口的指针,当调用 executeStrategy 方法时,根据指针实际指向的具体策略类对象,会调用相应的 execute 方法,这就是多态的体现。
例如,在上面的示例中,Context 类中的 executeStrategy 方法:
void executeStrategy() {
if (strategy) {
strategy->execute();
}
}
这里的 strategy 指针可以指向 ConcreteStrategyA 或 ConcreteStrategyB 对象,当调用 strategy->execute() 时,会根据 strategy 实际指向的对象调用相应的 execute 方法,从而实现了不同策略的动态切换。
2.3 策略的动态切换
策略模式允许在运行时动态地切换策略。上下文类提供了 setStrategy 方法,通过调用这个方法,我们可以在程序运行过程中随时更换所使用的策略。
例如:
int main() {
ConcreteStrategyA strategyA;
ConcreteStrategyB strategyB;
Context context(&strategyA);
context.executeStrategy();
context.setStrategy(&strategyB);
context.executeStrategy();
return 0;
}
在这个 main 函数中,我们先使用 ConcreteStrategyA 作为策略,然后通过 setStrategy 方法将策略切换为 ConcreteStrategyB,实现了策略的动态切换。
2.4 策略的选择与决策
在实际应用中,如何选择合适的策略是一个重要的问题。通常有以下几种方式:
2.4.1 客户端直接选择
客户端根据具体的业务需求,直接选择合适的策略并传递给上下文类。例如:
if (conditionA) {
context.setStrategy(new ConcreteStrategyA());
} else if (conditionB) {
context.setStrategy(new ConcreteStrategyB());
}
2.4.2 配置文件或数据库
可以将策略的选择信息存储在配置文件或数据库中,程序在运行时读取这些信息来选择合适的策略。例如,配置文件中可能有一个字段表示当前使用的策略名称,程序根据这个名称来创建相应的策略对象。
2.4.3 工厂模式结合
使用工厂模式来创建策略对象。工厂类根据一定的规则(如输入参数、配置信息等)来创建合适的策略对象,然后将其传递给上下文类。
class StrategyFactory {
public:
static Strategy* createStrategy(const std::string& strategyName) {
if (strategyName == "A") {
return new ConcreteStrategyA();
} else if (strategyName == "B") {
return new ConcreteStrategyB();
}
return nullptr;
}
};
// 使用工厂创建策略
Strategy* strategy = StrategyFactory::createStrategy("A");
Context context(strategy);
三、策略模式的应用场景
3.1 典型应用场景
3.1.1 排序算法选择
在排序算法中,我们可能有多种排序算法可供选择,如冒泡排序、快速排序、归并排序等。不同的排序算法在不同的场景下有不同的性能表现。我们可以使用策略模式将每种排序算法封装成一个具体策略类,然后根据数据的特点(如数据量大小、数据的初始顺序等)选择合适的排序算法。
#include <iostream>
#include <vector>
// 策略接口:排序策略
class SortStrategy {
public:
virtual void sort(std::vector<int>& data) = 0;
virtual ~SortStrategy() {}
};
// 冒泡排序策略
class BubbleSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
int n = data.size();
for (int i = 0; i < n - 1; ++i) {
for (int j = 0; j < n - i - 1; ++j) {
if (data[j] > data[j + 1]) {
std::swap(data[j], data[j + 1]);
}
}
}
}
};
// 快速排序策略
class QuickSort : public SortStrategy {
private:
int partition(std::vector<int>& data, int low, int high) {
int pivot = data[high];
int i = low - 1;
for (int j = low; j < high; ++j) {
if (data[j] < pivot) {
++i;
std::swap(data[i], data[j]);
}
}
std::swap(data[i + 1], data[high]);
return i + 1;
}
void quickSort(std::vector<int>& data, int low, int high) {
if (low < high) {
int pi = partition(data, low, high);
quickSort(data, low, pi - 1);
quickSort(data, pi + 1, high);
}
}
public:
void sort(std::vector<int>& data) override {
quickSort(data, 0, data.size() - 1);
}
};
// 上下文类:排序上下文
class SortContext {
private:
SortStrategy* strategy;
public:
SortContext(SortStrategy* s) : strategy(s) {}
void setStrategy(SortStrategy* s) {
strategy = s;
}
void performSort(std::vector<int>& data) {
if (strategy) {
strategy->sort(data);
}
}
};
int main() {
std::vector<int> data = {5, 3, 8, 4, 2};
BubbleSort bubbleSort;
QuickSort quickSort;
SortContext context(&bubbleSort);
context.performSort(data);
for (int num : data) {
std::cout << num << " ";
}
std::cout << std::endl;
data = {5, 3, 8, 4, 2};
context.setStrategy(&quickSort);
context.performSort(data);
for (int num : data) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
3.1.2 加密算法选择
在数据加密领域,有多种加密算法,如 AES、DES、RSA 等。不同的加密算法有不同的特点和适用场景。我们可以使用策略模式将每种加密算法封装成一个具体策略类,然后根据数据的安全性要求、性能要求等选择合适的加密算法。
3.1.3 支付方式选择
在电商系统中,用户可以选择多种支付方式,如支付宝、微信支付、银行卡支付等。每种支付方式的处理逻辑不同,我们可以使用策略模式将每种支付方式的处理逻辑封装成一个具体策略类,然后根据用户的选择调用相应的支付策略。
3.2 企业级应用案例
3.2.1 电商促销活动
在电商系统中,经常会有各种促销活动,如满减、折扣、赠品等。每种促销活动的计算规则不同,我们可以使用策略模式将每种促销活动的计算规则封装成一个具体策略类。当用户结算购物车时,根据当前的促销活动选择合适的策略来计算最终的价格。
#include <iostream>
#include <vector>
// 商品类
class Product {
public:
std::string name;
double price;
Product(const std::string& n, double p) : name(n), price(p) {}
};
// 促销策略接口
class PromotionStrategy {
public:
virtual double calculateDiscount(const std::vector<Product>& products) = 0;
virtual ~PromotionStrategy() {}
};
// 满减策略
class FullReductionStrategy : public PromotionStrategy {
private:
double fullAmount;
double reductionAmount;
public:
FullReductionStrategy(double full, double reduction) : fullAmount(full), reductionAmount(reduction) {}
double calculateDiscount(const std::vector<Product>& products) override {
double total = 0;
for (const auto& product : products) {
total += product.price;
}
if (total >= fullAmount) {
return reductionAmount;
}
return 0;
}
};
// 折扣策略
class DiscountStrategy : public PromotionStrategy {
private:
double discountRate;
public:
DiscountStrategy(double rate) : discountRate(rate) {}
double calculateDiscount(const std::vector<Product>& products) override {
double total = 0;
for (const auto& product : products) {
total += product.price;
}
return total * (1 - discountRate);
}
};
// 上下文类:购物车上下文
class ShoppingCartContext {
private:
PromotionStrategy* strategy;
std::vector<Product> products;
public:
ShoppingCartContext(PromotionStrategy* s) : strategy(s) {}
void setStrategy(PromotionStrategy* s) {
strategy = s;
}
void addProduct(const Product& product) {
products.push_back(product);
}
double calculateFinalPrice() {
double total = 0;
for (const auto& product : products) {
total += product.price;
}
if (strategy) {
double discount = strategy->calculateDiscount(products);
total -= discount;
}
return total;
}
};
int main() {
Product p1("Apple", 5.0);
Product p2("Banana", 3.0);
Product p3("Orange", 4.0);
FullReductionStrategy fullReduction(10, 2);
DiscountStrategy discount(0.8);
ShoppingCartContext cart(&fullReduction);
cart.addProduct(p1);
cart.addProduct(p2);
cart.addProduct(p3);
std::cout << "Final price with full reduction: " << cart.calculateFinalPrice() << std::endl;
cart.setStrategy(&discount);
std::cout << "Final price with discount: " << cart.calculateFinalPrice() << std::endl;
return 0;
}
3.2.2 物流配送策略
在物流系统中,有多种配送方式,如快递、平邮、同城配送等。每种配送方式的收费规则、配送时间等不同,我们可以使用策略模式将每种配送方式的处理逻辑封装成一个具体策略类,根据商品的重量、体积、目的地等信息选择合适的配送策略。
3.3 模式适用条件
当系统满足以下条件时,适合使用策略模式:
系统中有多种算法可以完成同一个任务,并且需要在运行时动态地选择合适的算法。
算法的使用和算法的实现需要分离,以提高代码的可维护性和可扩展性。
避免使用大量的条件语句来选择不同的算法,使代码更加清晰和易于理解。
四、策略模式的使用方法
4.1 实现步骤详解
4.1.1 定义策略接口
首先,我们需要定义一个策略接口,该接口定义了所有具体策略类必须实现的方法。
// 策略接口
class Strategy {
public:
virtual void execute() = 0;
virtual ~Strategy() {}
};
4.1.2 实现具体策略类
根据策略接口,实现具体的策略类,每个具体策略类封装了一种具体的算法。
// 具体策略类A
class ConcreteStrategyA : public Strategy {
public:
void execute() override {
std::cout << "Executing strategy A" << std::endl;
}
};
// 具体策略类B
class ConcreteStrategyB : public Strategy {
public:
void execute() override {
std::cout << "Executing strategy B" << std::endl;
}
};
4.1.3 创建上下文类
创建上下文类,该类持有一个策略接口的引用,负责根据客户端的需求选择合适的策略,并调用该策略的方法。
// 上下文类
class Context {
private:
Strategy* strategy;
public:
Context(Strategy* s) : strategy(s) {}
void setStrategy(Strategy* s) {
strategy = s;
}
void executeStrategy() {
if (strategy) {
strategy->execute();
}
}
};
4.1.4 使用策略模式
在客户端代码中,创建具体策略对象和上下文对象,选择合适的策略并调用上下文对象的方法来执行策略。
int main() {
ConcreteStrategyA strategyA;
ConcreteStrategyB strategyB;
Context context(&strategyA);
context.executeStrategy();
context.setStrategy(&strategyB);
context.executeStrategy();
return 0;
}
4.2 代码实现技巧
4.2.1 使用智能指针管理策略对象
为了避免内存泄漏,我们可以使用智能指针(如 std::unique_ptr 或 std::shared_ptr)来管理策略对象。
#include <iostream>
#include <memory>
// 策略接口
class Strategy {
public:
virtual void execute() = 0;
virtual ~Strategy() {}
};
// 具体策略类A
class ConcreteStrategyA : public Strategy {
public:
void execute() override {
std::cout << "Executing strategy A" << std::endl;
}
};
// 具体策略类B
class ConcreteStrategyB : public Strategy {
public:
void execute() override {
std::cout << "Executing strategy B" << std::endl;
}
};
// 上下文类
class Context {
private:
std::unique_ptr<Strategy> strategy;
public:
Context(std::unique_ptr<Strategy> s) : strategy(std::move(s)) {}
void setStrategy(std::unique_ptr<Strategy> s) {
strategy = std::move(s);
}
void executeStrategy() {
if (strategy) {
strategy->execute();
}
}
};
int main() {
auto strategyA = std::make_unique<ConcreteStrategyA>();
auto strategyB = std::make_unique<ConcreteStrategyB>();
Context context(std::move(strategyA));
context.executeStrategy();
context.setStrategy(std::move(strategyB));
context.executeStrategy();
return 0;
}
4.2.2 结合工厂模式创建策略对象
如前面提到的,使用工厂模式可以更方便地创建策略对象。
#include <iostream>
#include <memory>
// 策略接口
class Strategy {
public:
virtual void execute() = 0;
virtual ~Strategy() {}
};
// 具体策略类A
class ConcreteStrategyA : public Strategy {
public:
void execute() override {
std::cout << "Executing strategy A" << std::endl;
}
};
// 具体策略类B
class ConcreteStrategyB : public Strategy {
public:
void execute() override {
std::cout << "Executing strategy B" << std::endl;
}
};
// 策略工厂类
class StrategyFactory {
public:
static std::unique_ptr<Strategy> createStrategy(const std::string& strategyName) {
if (strategyName == "A") {
return std::make_unique<ConcreteStrategyA>();
} else if (strategyName == "B") {
return std::make_unique<ConcreteStrategyB>();
}
return nullptr;
}
};
// 上下文类
class Context {
private:
std::unique_ptr<Strategy> strategy;
public:
Context(std::unique_ptr<Strategy> s) : strategy(std::move(s)) {}
void setStrategy(std::unique_ptr<Strategy> s) {
strategy = std::move(s);
}
void executeStrategy() {
if (strategy) {
strategy->execute();
}
}
};
int main() {
auto strategyA = StrategyFactory::createStrategy("A");
Context context(std::move(strategyA));
context.executeStrategy();
auto strategyB = StrategyFactory::createStrategy("B");
context.setStrategy(std::move(strategyB));
context.executeStrategy();
return 0;
}
4.2.3 策略参数化
有时候,具体策略类的算法可能需要一些参数。我们可以在策略接口或具体策略类中添加相应的参数。
#include <iostream>
#include <memory>
// 策略接口
class Strategy {
public:
virtual void execute(int param) = 0;
virtual ~Strategy() {}
};
// 具体策略类A
class ConcreteStrategyA : public Strategy {
public:
void execute(int param) override {
std::cout << "Executing strategy A with parameter: " << param << std::endl;
}
};
// 具体策略类B
class ConcreteStrategyB : public Strategy {
public:
void execute(int param) override {
std::cout << "Executing strategy B with parameter: " << param << std::endl;
}
};
// 上下文类
class Context {
private:
std::unique_ptr<Strategy> strategy;
public:
Context(std::unique_ptr<Strategy> s) : strategy(std::move(s)) {}
void setStrategy(std::unique_ptr<Strategy> s) {
strategy = std::move(s);
}
void executeStrategy(int param) {
if (strategy) {
strategy->execute(param);
}
}
};
int main() {
auto strategyA = std::make_unique<ConcreteStrategyA>();
Context context(std::move(strategyA));
context.executeStrategy(10);
auto strategyB = std::make_unique<ConcreteStrategyB>();
context.setStrategy(std::move(strategyB));
context.executeStrategy(20);
return 0;
}
五、常见问题及解决方案
5.1 常见问题分析
5.1.1 策略类数量过多
当系统中有很多种算法时,会导致具体策略类的数量过多,增加了代码的维护难度。
5.1.2 客户端需要了解所有策略类
客户端需要知道所有的具体策略类,并且在选择策略时需要进行复杂的判断,这增加了客户端代码的复杂度。
5.1.3 策略类之间的依赖关系
有些策略类可能依赖于其他的类或资源,这会导致策略类之间的耦合度增加,影响代码的可维护性和可扩展性。
5.1.4 策略的初始化和销毁
如果策略类需要进行复杂的初始化和销毁操作,那么在上下文类中管理这些操作会变得困难。
5.2 解决方案
5.2.1 策略类的合并与分组
对于一些功能相似的策略类,可以将它们合并成一个策略类,或者将它们分组,减少策略类的数量。例如,将一些排序算法中通用的部分提取出来,放在一个基类中,然后不同的排序算法继承自这个基类。
5.2.2 使用工厂模式或配置文件
通过工厂模式或配置文件来隐藏具体策略类的创建过程,客户端只需要提供一个简单的标识(如策略名称),由工厂类或配置文件来创建相应的策略对象。这样可以降低客户端代码的复杂度。
5.2.3 依赖注入
使用依赖注入的方式来解决策略类之间的依赖关系。将策略类依赖的对象通过构造函数或方法参数传递给策略类,而不是在策略类内部创建这些对象。这样可以降低策略类之间的耦合度。
5.2.4 资源管理类
对于需要进行复杂初始化和销毁操作的策略类,可以创建一个资源管理类来管理这些操作。资源管理类负责策略类的初始化和销毁,上下文类只需要与资源管理类交互,而不需要关心具体的初始化和销毁细节。
以下是一个使用依赖注入和资源管理类的示例:
#include <iostream>
#include <memory>
// 依赖的资源类
class Resource {
public:
Resource() {
std::cout << "Resource initialized" << std::endl;
}
~Resource() {
std::cout << "Resource destroyed" << std::endl;
}
void use() {
std::cout << "Using resource" << std::endl;
}
};
// 策略接口
class Strategy {
public:
virtual void execute() = 0;
virtual ~Strategy() {}
};
// 具体策略类
class ConcreteStrategy : public Strategy {
private:
std::shared_ptr<Resource> resource;
public:
ConcreteStrategy(std::shared_ptr<Resource> r) : resource(r) {}
void execute() override {
if (resource) {
resource->use();
}
}
};
// 资源管理类
class ResourceManager {
private:
std::shared_ptr<Resource> resource;
public:
ResourceManager() : resource(std::make_shared<Resource>()) {}
std::shared_ptr<Resource> getResource() {
return resource;
}
};
// 上下文类
class Context {
private:
std::unique_ptr<Strategy> strategy;
public:
Context(std::unique_ptr<Strategy> s) : strategy(std::move(s)) {}
void executeStrategy() {
if (strategy) {
strategy->execute();
}
}
};
int main() {
ResourceManager resourceManager;
auto resource = resourceManager.getResource();
auto strategy = std::make_unique<ConcreteStrategy>(resource);
Context context(std::move(strategy));
context.executeStrategy();
return 0;
}
在这个示例中,Resource 类是策略类依赖的资源类,ConcreteStrategy 类通过构造函数接收一个 std::shared_ptr 对象,实现了依赖注入。ResourceManager 类负责资源的初始化和管理,Context 类只需要与 ConcreteStrategy 类交互,不需要关心资源的初始化和销毁细节。
六、总结
6.1 模式优点
- 可扩展性强:当需要新增一种算法时,只需要创建一个新的具体策略类,而不需要修改现有的代码,符合开闭原则。
- 代码复用性高:每个具体策略类封装了自己的算法,这些算法可以在不同的地方复用。
- 可维护性好:策略模式将算法的定义和使用分离,使代码结构更加清晰,易于维护。
- 灵活性高:可以在运行时动态地切换策略,根据不同的需求选择合适的算法。
6.2 模式缺点
- 策略类数量过多:当系统中有很多种算法时,会导致具体策略类的数量过多,增加了代码的维护难度。
- 客户端复杂度增加:客户端需要了解所有的具体策略类,并且在选择策略时需要进行复杂的判断,增加了客户端代码的复杂度。
- 内存开销:每个具体策略类都需要占用一定的内存空间,如果策略类数量过多,会增加内存开销。
6.3 最佳实践建议
- 合理设计策略接口:策略接口应该定义清晰,只包含必要的方法,避免接口过于复杂。
- 使用工厂模式或配置文件:通过工厂模式或配置文件来隐藏具体策略类的创建过程,降低客户端代码的复杂度。
- 注意策略类的生命周期管理:使用智能指针等方式来管理策略类的生命周期,避免内存泄漏。
- 控制策略类的数量:对于功能相似的策略类,可以进行合并或分组,减少策略类的数量。
6.4 未来发展趋势
随着软件系统的不断发展,策略模式可能会与其他设计模式结合使用,以满足更复杂的需求。例如,与状态模式结合,根据系统的不同状态选择不同的策略;与观察者模式结合,当系统的某些条件发生变化时,动态地切换策略。
同时,随着人工智能和机器学习的发展,策略模式可能会在算法选择和优化方面发挥更大的作用,根据数据的特点和模型的性能选择合适的算法。
策略模式是一种非常实用的设计模式,它可以帮助我们提高代码的可维护性、可扩展性和灵活性。在实际应用中,我们应该根据具体的需求和场景,合理地使用策略模式,并结合其他设计模式和技术,构建出更加健壮和高效的软件系统。