策略模式(Strategy)

news2024/11/16 9:40:26

定义

策略是一种行为设计模式,它能让你定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换。

前言

1. 问题

你打算为游客们创建一款导游程序。该程序的核心功能是提供美观的地图,以帮助用户在任何城市中快速定位。

用户期待的程序新功能是自动路线规划:他们希望输入地址后就能在地图上看到前往目的地的最快路线。

程序的首个版本只能规划公路路线。驾车旅行的人们对此非常满意。但很显然,并非所有人都会在度假时开车。因此你在下次更新时添加了规划步行路线的功能。此后,你又添加了规划公共交通路线的功能。

而这只是个开始。不久后,你又要为骑行者规划路线。又过了一段时间,你又要为游览城市中的所有景点规划路线。

尽管从商业角度来看,这款应用非常成功,但其技术部分却让你非常头疼:每次添加新的路线规划算法后,导游应用中主要类的体积就会增加一倍。终于在某个时候,你觉得自己没法继续维护这堆代码了。

无论是修复简单缺陷还是微调街道权重,对某个算法进行任何修改都会影响整个类,从而增加在已有正常运行代码中引入错误的风险。

此外,团队合作将变得低效。如果你在应用成功发布后招募了团队成员,他们会抱怨在合并冲突的工作上花费了太多时间。在实现新功能的过程中,你的团队需要修改同一个巨大的类,这样他们所编写的代码相互之间就可能会出现冲突。

2. 解决方案

策略模式建议找出负责用许多不同方式完成特定任务的类,然后将其中的算法抽取到一组被称为策略的独立类中。

名为上下文的原始类必须包含一个成员变量来存储对于每种策略的引用。上下文并不执行任务,而是将工作委派给已连接的策略对象。

上下文不负责选择符合任务需要的算法——客户端会将所需策略传递给上下文。实际上,上下文并不十分了解策略,它会通过同样的通用接口与所有策略进行交互,而该接口只需暴露一个方法来触发所选策略中封装的算法即可。

因此,上下文可独立于具体策略。这样你就可在不修改上下文代码或其他策略的情况下添加新算法或修改已有算法了。

在导游应用中, 每个路线规划算法都可被抽取到只有一个buildRoute方法的独立类中。该方法接收起点和终点作为参数,并返回路线中途点的集合。

即使传递给每个路径规划类的参数一模一样,其所创建的路线也可能完全不同。主要导游类的主要工作是在地图上渲染一系列中途点,不会在意如何选择算法。该类中还有一个用于切换当前路径规划策略的方法,因此客户端(例如用户界面中的按钮)可用其他策略替换当前选择的路径规划行为。

结构

  1. 上下文(Context)维护指向具体策略的引用,且仅通过策略接口与该对象进行交流
  2. 策略(Strategy)接口是所有具体策略的通用接口,它声明了一个上下文用于执行策略的方法。
  3. 具体策略(Concrete Strategies)实现了上下文所用算法的各种不同变体。
  4. 当上下文需要运行算法时,它会在其已连接的策略对象上调用执行方法。上下文不清楚其所涉及的策略类型与算法的执行方式。
  5. 客户端(Client)会创建一个特定策略对象并将其传递给上下文。上下文则会提供一个设置器以便客户端在运行时替换相关联的策略。

适用场景

  • 当你想使用对象中各种不同的算法变体,并希望能在运行时切换算法时,可使用策略模式。

策略模式让你能够将对象关联至可以不同方式执行特定子任务的不同子对象,从而以间接方式在运行时更改对象行为。

  • 当你有许多仅在执行某些行为时略有不同的相似类时,可使用策略模式。

策略模式让你能将不同行为抽取到一个独立类层次结构中,并将原始类组合成同一个,从而减少重复代码。

  • 如果算法在上下文的逻辑中不是特别重要,使用该模式能将类的业务逻辑与其算法实现细节隔离开来。

策略模式让你能将各种算法的代码、内部数据和依赖关系与其他代码隔离开来。不同客户端可通过一个简单接口执行算法,并能在运行时进行切换。

  • 当类中使用了复杂条件运算符以在同一算法的不同变体中切换时,可使用该模式。

策略模式将所有继承自同样接口的算法抽取到独立类中,因此不再需要条件语句。 原始对象并不实现所有算法的变体,而是将执行工作委派给其中的一个独立算法对象。

实现方式

  1. 从上下文类中找出修改频率较高的算法(也可能是用于在运行时选择某个算法变体的复杂条件运算符)。
  2. 声明该算法所有变体的通用策略接口。
  3. 将算法逐一抽取到各自的类中,它们都必须实现策略接口。
  4. 在上下文类中添加一个成员变量用于保存对于策略对象的引用。然后提供设置器以修改该成员变量。上下文仅可通过策略接口同策略对象进行交互,如有需要还可定义一个接口来让策略访问其数据。
  5. 客户端必须将上下文类与相应策略进行关联,使上下文可以预期的方式完成其主要工作。

优点

  • 你可以在运行时切换对象内的算法。
  • 你可以将算法的实现和使用算法的代码隔离开来。
  • 你可以使用组合来代替继承。
  • 开闭原则。你无需对上下文进行修改就能够引入新的策略。

缺点

  • 如果你的算法极少发生改变,那么没有任何理由引入新的类和接口。使用该模式只会让程序过于复杂。
  • 客户端必须知晓策略间的不同——它需要选择合适的策略。
  • 许多现代编程语言支持函数类型功能,允许你在一组匿名函数中实现不同版本的算法。这样,你使用这些函数的方式就和使用策略对象时完全相同,无需借助额外的类和接口来保持代码简洁

Strategy.hpp

#ifndef STRATEGY_H_
#define STRATEGY_H_

#include <vector>

// 抽象策略类: 排序
class Sort {
 public:
    virtual void sortVector(std::vector<int> &arr) = 0;
};

#endif  // STRATEGY_H_

ConcreteStrategy.hpp

#ifndef CONCRETE_STRATEGY_H_
#define CONCRETE_STRATEGY_H_

#include <vector>
#include <string>
#include <utility>
#include <iostream>
#include "Strategy.hpp"

// 打印vector内容
void printVector(const std::string prefix, const std::vector<int> &vi) {
    std::cout << prefix;
    for (auto i : vi) {
        std::cout << " " << i;
    }
    std::cout << std::endl;
}

// 具体策略类: 冒泡排序
class BubbleSort : public Sort {
 public:
    void sortVector(std::vector<int> &vi) override {
        printVector("冒泡排序前:", vi);
        int len = vi.size();
        // 轮次: 从1到n-1轮
        for (int i = 0; i < len - 1; ++i) {
            // 优化: 判断本轮是否有交换元素, 如果没交换则可直接退出
            bool is_exchange = false;

            for (int j = 0; j < len - i - 1; ++j) {
                if (vi[j] > vi[j+1]) {
                    std::swap(vi[j], vi[j+1]);
                    is_exchange = true;
                }
            }

            // 如果本轮无交换, 则可以直接退出
            if (!is_exchange) {
                printVector("冒泡排序后:", vi);
                return;
            }
        }
        printVector("冒泡排序后:", vi);
    }
};

// 具体策略类: 选择排序
class SelectionSort : public Sort {
 public:
    void sortVector(std::vector<int> &vi) override {
        printVector("选择排序前:", vi);
        // 需要进行 n-1 轮
        for (int i = 0; i < vi.size() - 1; ++i) {
            // 找到此轮的最小值下标
            int min_index = i;
            for (int j = i + 1; j < vi.size(); ++j) {
                if (vi[j] < vi[min_index]) {
                    min_index = j;
                }
            }

            std::swap(vi[i], vi[min_index]);
        }
        printVector("选择排序后:", vi);
    }
};

// 具体策略类: 插入排序
class InsertionSort : public Sort {
 public:
    void sortVector(std::vector<int> &vi) override {
        printVector("插入排序前:", vi);
        // 第一轮不需要操作, 第二轮比较一次, 第n轮比较 n-1 次
        for (int i = 1; i < vi.size(); ++i) {
            // 存储待插入的值和下标
            int insert_value = vi[i];
            int j = i - 1;

            while (j >= 0 && vi[j] > insert_value) {
                vi[j + 1] = vi[j];  // 如果左侧的已排序元素比目标值大, 那么右移
                j--;
            }

            // 注意这里insert_index 需要+1
            vi[j + 1] = insert_value;
        }
        printVector("插入排序后:", vi);
    }
};

#endif  // CONCRETE_STRATEGY_H_

Context.hpp

#ifndef CONTEXT_H_
#define CONTEXT_H_

#include <vector>
#include "Strategy.hpp"

class ArrayHandler {
 public:
    void sortVector(std::vector<int> &arr) {
        return sort_->sortVector(arr);
    }
    void setSortStrategy(Sort* sort) {
        sort_ = sort;
    }

 private:
    Sort *sort_;
};

#endif  // CONTEXT_H_

main.cpp

#include <vector>
#include <algorithm>
#include <random>
#include <iostream>
#include "ConcreteStrategy.hpp"
#include "Context.hpp"

std::vector<int> test_array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25};

int main() {
    ArrayHandler array_handler;

    {
        // 冒泡排序
        BubbleSort* bubble_sort = new BubbleSort();
        auto rng = std::default_random_engine {};
        std::shuffle(std::begin(test_array), std::end(test_array), rng);
        array_handler.setSortStrategy(bubble_sort);
        array_handler.sortVector(test_array);
        delete bubble_sort;
    }

    {
        // 选择排序
        SelectionSort* select_sort = new SelectionSort();
        auto rng = std::default_random_engine {};
        std::shuffle(std::begin(test_array), std::end(test_array), rng);
        array_handler.setSortStrategy(select_sort);
        array_handler.sortVector(test_array);
        delete select_sort;
    }

    {
        // 插入排序
        InsertionSort* insert_sort = new InsertionSort();
        auto rng = std::default_random_engine {};
        std::shuffle(std::begin(test_array), std::end(test_array), rng);
        array_handler.setSortStrategy(insert_sort);
        array_handler.sortVector(test_array);
        delete insert_sort;
    }


    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/707802.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构 手撕顺序表(动态版)+代码详解

⭐️ 顺序表介绍 顺序表是线性表的一种。 &#x1f320;什么是线性表呢&#xff1f; 线性表是数据结构的一种&#xff0c;一个线性表是 n n n个具有相同特性的数据元素的有限序列。常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串… &#x1f320;什么是顺序表呢&…

Docker + Wasm = 王炸!!!

Docker 宣布推出与 WebAssembly 集成 (DockerWasm) 的首个技术预览版&#xff0c;并表示公司已加入字节码联盟 (Bytecode Alliance)&#xff0c;成为投票成员。 Bytecode Alliance&#xff08;字节码联盟&#xff09;由 Mozilla、Fastly、Intel 与 Red Hat 联合成立&#xff0c…

Redis的优化(二)

Redis的高可用 一、主从复制优化主从复制的作用主从复制流程主从复制实验 二、Redis 哨兵模式哨兵模式的作用故障转移机制主节点的选举原则哨兵模式的实验 三、Redis群集模式集群的作用Redis集群的数据分片搭建Redis群集模式实验 ●主从复制&#xff1a;主从复制是高可用Redis的…

路由协议基本术语

文章目录 1、自治系统AS2、EGP和IGP3、度量标准和度量值4、管理距离5、路由协议与路由算法6、路由环路问题 1、自治系统AS Internet中&#xff0c;自治系统就是处于同一个管理机构&#xff08;如一个ISP&#xff09;控制下的路由器和网络群组 在同一个自治系统中的所有路由器…

Python3 标准库概览 | 菜鸟教程(十八)

目录 一、Python3 标准库中的模块 &#xff08;一&#xff09;os 模块 &#xff08;二&#xff09;sys 模块 &#xff08;三&#xff09;time 模块 &#xff08;四&#xff09;datetime 模块 &#xff08;五&#xff09;random 模块 &#xff08;六&#xff09;math 模块…

RabbitMQ笔记(持续更新中~)

1.消息队列 1.1 MQ的相关概念 1.1.1 什么是MQ MQ&#xff08;message queue&#xff09;&#xff0c;从字面上看&#xff0c;本质是个队列&#xff0c;FIFO先进先出&#xff0c;只不过队列中存放的内容是消息而已&#xff0c;还是一种跨进程的通信机制&#xff0c;用于上下游…

Pandas之Series(一)

Hi&#x1f60a;&#x1f60a;~大家好呀~最近两天釉色酱在学习python中的数据分析的一个基本库——pandas。今天就先学习pandas中最基本的数据结构Series。下面我们一起进入Series的世界吧&#xff01;&#x1f61d; Pandas简介&#xff1a; Pandas是一种基于Python语言的快速…

sklearn.model_selection模块介绍

数据集划分方法 train_test_split train_test_split(*arrays, test_sizeNone, train_sizeNone, random_stateNone, shuffleTrue, stratifyNone)参数包括&#xff1a; test_size&#xff1a;可选参数&#xff0c;表示测试集的大小。可以是一个表示比例的浮点数&#xff08;例…

Android:ViewPager2

简介 ViewPager2内部使用RecyclerView实现&#xff0c;并提供了增强功能 特性 支持水平、垂直方向布局 android:orientation “vertical” 支持从右到左 android:layoutDirection “rtl” 禁止滑动 setUserInputEnabled() 可修改Fragment集合 对可修改的Fragment集合进行分…

深入探究Bean生命周期的扩展点:Bean Post Processor

概要 在Spring框架中&#xff0c;Bean生命周期的管理是非常重要的一部分。在Bean的创建、初始化和销毁过程中&#xff0c;Spring提供了一系列的扩展点&#xff0c;使开发者能够在不破坏原有功能的基础上&#xff0c;对Bean的生命周期进行定制化操作。其中&#xff0c;Bean Post…

LLM记录202304-202306

RLHF RAFT RAFT: Reward rAnked FineTuning for Generative Foundation Model Alignment code RRHF RRHF: Rank Responses to Align Language Models with Human Feedback without tears code p i = ∑ t lo

English Learning - L3 作业打卡 Lesson7 Day53 2023.6.28 周三

English Learning - L3 作业打卡 Lesson7 Day53 2023.6.28 周三 引言&#x1f349;句1: It was this moment that I asked myself that life-defining question:成分划分同化连读爆破语调 &#x1f349;句2: If my life were a book and I were the author, how would I want t…

基于Web的小学学科数字教学资源管理系统

摘要 小学学科数字教学资源管理是一个典型的学习项目&#xff0c;从教学资源、教材信息的统计和分析&#xff0c;在过程中会产生大量的、各种各样的数据。本文以小学学科数字教学资源管理系统为目标&#xff0c;采用B/S模式&#xff0c;以Springboot为开发框架&#xff0c;java…

计算机网络面经之TCP三次握手和四次挥手的详解

常见问题 1.详细描述三次握手和四次挥手的过程。 2.三次握手可以变成两次握手吗&#xff1f; 3.简述 TCP 连接和关闭的状态转移。 4.简述TCP 四次挥手的 TIME_WAIT状态&#xff0c;以及为什么需要有这个状态 重要的字段定义与作用 &#xff08;1&#xff09;序号(sequence nu…

循环双链表

目录 双向循环链表结构体初始化函数添加数据头插删除数据显示函数示例程序一(简易版本)&#xff1a;运行结果&#xff1a;示例程序二输出结果&#xff1a; 双向循环链表 结构图示&#xff1a; 结构体 typedef struct node {int data;struct node* pre; //指向前驱struct …

C++迭代器

目录 1.iterator 2.数组 1.iterator 迭代器就是个内置指针&#xff0c;可以 -- &#xff0c;可以解引用。 迭代器分两种类型 iterator 和const_iterator&#xff08;只读&#xff0c;不能修改&#xff09; 迭代器要用作用域限定类型 vector<int>::iterator it; 如果不限制…

Yarn的实现原理详解

概要 Yarn作为分布式集群的资源调度框架&#xff0c;它的出现伴随着Hadoop的发展&#xff0c;使Hadoop从一个单一的大数据计算引擎&#xff0c;成为一个集存储、计算、资源管理为一体的完整大数据平台&#xff0c;进而发展出自己的生态体系&#xff0c;成为大数据的代名词。 Ya…

C++11新特性 智能指针

智能指针 nuique_ptr特点不允许拷贝构造和赋值运算符重载-> () *unique_ptr 删除器仿写删除文件删除普通对象 shared_ptr特点示意图仿写shared_ptr删除器部分特化拷贝构造 移动构造 && 左值赋值 和移动赋值完整实现 weak_ptr特点weak_ptr 实现解决循环引用弱指针一个…

java: 警告: 源发行版 11 需要目标发行版 11解决方案

出现这样的问题首先检查一下自己的项目结构是否使用的对应的jdk 如果这里是正确的&#xff0c;之后查看一下自己的pom文件中是否指定了正确的jdk 这里的时候你改完运行就会发现还会报错&#xff0c;一定要记得刷新一下maven 再重新启动项目&#xff0c;即解决

剑指 Offer 63: 股票的最大利润

最标准答案 不可以有前一项的影响&#xff0c;只能用来对比并不叠加 这里max设置0就会导致先行进入大于max的判断语句&#xff01; 无语了&#xff0c;自己把问题想的太复杂了&#xff01; class Solution {public int maxProfit(int[] prices) {if(prices.length<2) retur…