设计模式——策略模式

发布于:2024-11-27 ⋅ 阅读:(9) ⋅ 点赞:(0)

定义与概念

策略模式(Strategy Pattern)是一种行为设计模式。它定义了一系列算法,将每个算法都封装起来,并且使它们可以相互替换。此模式让算法的变化独立于使用算法的客户。
简单来说,就好比在一个角色扮演游戏中,角色的攻击行为可以有多种策略,如近战攻击、远程攻击、魔法攻击等。这些不同的攻击策略可以被封装成不同的类,并且可以在游戏运行过程中根据实际情况(如角色装备的武器、技能等)进行切换。

结构组成

  • 策略(Strategy)接口:
    这是所有具体策略类的共同接口,它定义了策略方法的签名。例如,在一个路径规划系统中,策略接口可能定义了一个findPath()方法,用于寻找从一个点到另一个点的路径。
  • 具体策略(Concrete Strategy)类:
    实现了策略接口,提供了具体的算法实现。在路径规划系统中,可能有 “最短路径策略” 类,它的findPath()方法会使用迪杰斯特拉算法来寻找最短路径;还有 “随机路径策略” 类,它的findPath()方法会随机生成一条可行路径。
  • 上下文(Context)类:
    它持有一个策略接口的引用,用于调用具体的策略方法。上下文类的主要职责是维护策略对象,并在需要时调用策略对象的方法。在游戏角色攻击的例子中,游戏角色类就是上下文类,它维护一个攻击策略对象的引用,当需要进行攻击时,就调用这个攻击策略对象的attack()方法。

工作原理

客户端(使用策略的代码)创建一个具体策略对象,并将其传递给上下文类。上下文类在执行相关操作时,通过其持有的策略接口引用调用具体策略对象的方法。
例如,在一个文本格式化系统中,有 “加粗格式策略” 和 “斜体格式策略” 等具体策略类。上下文类(文本格式化器)可以接收用户选择的具体格式策略对象,当用户要求格式化文本时,文本格式化器就调用当前策略对象的format()方法来对文本进行相应的格式化处理。

代码示例

首先是策略接口,以排序策略为例:

class SortStrategy {
public:
    virtual void sort(int arr[], int size) = 0;
};

具体策略类 - 冒泡排序:

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;
                }
            }
        }
    }
};

具体策略类 - 快速排序:

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);
            }
        }
    }
};

上下文类:

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);
    }
};

使用示例:

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;
}

优点

  • 可替换性:
    可以方便地替换算法(策略)。在上述排序的例子中,如果发现一种新的更高效的排序算法,只需要创建一个新的具体策略类实现排序策略接口,然后在上下文中替换原来的策略即可,不需要修改上下文类的其他部分。
  • 可维护性和可扩展性:
    将不同的算法封装在各自的类中,使得代码结构清晰,易于维护和扩展。如果要修改某个排序算法的实现,只需要在对应的具体策略类中进行修改,不会影响到其他策略和上下文类的大部分代码。
  • 符合开闭原则:
    对扩展开放,对修改关闭。当需要添加新的策略时,只需要实现策略接口,创建新的具体策略类,而不需要修改已有的代码。

缺点

  • 增加类的数量:
    每一个策略都需要一个具体策略类来实现,当策略较多时,会导致类的数量增加,使得代码的复杂性提高。例如,在一个复杂的金融交易系统中,如果有大量的交易策略,会产生很多策略类文件。
  • 客户端需要了解策略:
    客户端需要知道有哪些策略可供选择,并且需要知道如何创建和使用这些策略。在一些复杂的系统中,这可能会增加客户端代码的复杂性,尤其是当策略的选择和使用条件比较复杂时。