定义与概念
策略模式(Strategy Pattern)是一种行为设计模式。它定义了一系列算法,将每个算法都封装起来,并且使它们可以相互替换。此模式让算法的变化独立于使用算法的客户。
简单来说,就好比在一个角色扮演游戏中,角色的攻击行为可以有多种策略,如近战攻击、远程攻击、魔法攻击等。这些不同的攻击策略可以被封装成不同的类,并且可以在游戏运行过程中根据实际情况(如角色装备的武器、技能等)进行切换。
结构组成
- 策略(Strategy)接口:
这是所有具体策略类的共同接口,它定义了策略方法的签名。例如,在一个路径规划系统中,策略接口可能定义了一个findPath()方法,用于寻找从一个点到另一个点的路径。 - 具体策略(Concrete Strategy)类:
实现了策略接口,提供了具体的算法实现。在路径规划系统中,可能有 "最短路径策略" 类,它的findPath()方法会使用迪杰斯特拉算法来寻找最短路径;还有 "随机路径策略" 类,它的findPath()方法会随机生成一条可行路径。 - 上下文(Context)类:
它持有一个策略接口的引用,用于调用具体的策略方法。上下文类的主要职责是维护策略对象,并在需要时调用策略对象的方法。在游戏角色攻击的例子中,游戏角色类就是上下文类,它维护一个攻击策略对象的引用,当需要进行攻击时,就调用这个攻击策略对象的attack()方法。
工作原理
客户端(使用策略的代码)创建一个具体策略对象,并将其传递给上下文类。上下文类在执行相关操作时,通过其持有的策略接口引用调用具体策略对象的方法。
例如,在一个文本格式化系统中,有 "加粗格式策略" 和 "斜体格式策略" 等具体策略类。上下文类(文本格式化器)可以接收用户选择的具体格式策略对象,当用户要求格式化文本时,文本格式化器就调用当前策略对象的format()方法来对文本进行相应的格式化处理。
代码示例
首先是策略接口,以排序策略为例:
cpp
class SortStrategy {
public:
virtual void sort(int arr[], int size) = 0;
};
具体策略类 - 冒泡排序:
cpp
class BubbleSortStrategy : public SortStrategy {
public:
void sort(int arr[], int size) override {
for (int i = 0; i < size - 1; ++i) {
for (int j = 0; j < size - i - 1; ++j) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
};
具体策略类 - 快速排序:
cpp
class QuickSortStrategy : public SortStrategy {
public:
int partition(int arr[], int low, int high) {
int pivot = arr[high];
int i = (low - 1);
for (int j = low; j <= high - 1; ++j) {
if (arr[j] <= pivot) {
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return (i + 1);
}
void sort(int arr[], int size) override {
std::stack<int> stack;
stack.push(0);
stack.push(size - 1);
while (!stack.empty()) {
int high = stack.top();
stack.pop();
int low = stack.top();
stack.pop();
int p = partition(arr, low, high);
if (p - 1 > low) {
stack.push(low);
stack.push(p - 1);
}
if (p + 1 < high) {
stack.push(p + 1);
stack.push(high);
}
}
}
};
上下文类:
cpp
class Sorter {
private:
SortStrategy* strategy;
public:
Sorter(SortStrategy* s) : strategy(s) {}
void setStrategy(SortStrategy* s) {
strategy = s;
}
void sort(int arr[], int size) {
strategy->sort(arr, size);
}
};
使用示例:
cpp
int main() {
int arr[] = {5, 4, 3, 2, 1};
int size = sizeof(arr) / sizeof(arr[0]);
BubbleSortStrategy bubbleSort;
Sorter sorter(&bubbleSort);
sorter.sort(arr, size);
for (int i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
QuickSortStrategy quickSort;
sorter.setStrategy(&quickSort);
sorter.sort(arr, size);
for (int i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
return 0;
}
优点
- 可替换性:
可以方便地替换算法(策略)。在上述排序的例子中,如果发现一种新的更高效的排序算法,只需要创建一个新的具体策略类实现排序策略接口,然后在上下文中替换原来的策略即可,不需要修改上下文类的其他部分。 - 可维护性和可扩展性:
将不同的算法封装在各自的类中,使得代码结构清晰,易于维护和扩展。如果要修改某个排序算法的实现,只需要在对应的具体策略类中进行修改,不会影响到其他策略和上下文类的大部分代码。 - 符合开闭原则:
对扩展开放,对修改关闭。当需要添加新的策略时,只需要实现策略接口,创建新的具体策略类,而不需要修改已有的代码。
缺点
- 增加类的数量:
每一个策略都需要一个具体策略类来实现,当策略较多时,会导致类的数量增加,使得代码的复杂性提高。例如,在一个复杂的金融交易系统中,如果有大量的交易策略,会产生很多策略类文件。 - 客户端需要了解策略:
客户端需要知道有哪些策略可供选择,并且需要知道如何创建和使用这些策略。在一些复杂的系统中,这可能会增加客户端代码的复杂性,尤其是当策略的选择和使用条件比较复杂时。