【设计模式】4、策略模式
文章目录
- 一、问题
- 二、解决方案
- 2.1 真实世界的类比
- 2.2 策略模式结构
- 2.3 适用场景
- 2.4 实现方式
- 2.5 优缺点
- 2.6 与其他模式的关系
- 三、示例代码
- 3.1 go
- 3.2 rust
策略模式是一种行为设计模式,它能定义一系列算法,把每种算法分别放入独立的类中,以是算法的对象能相互替换。
一、问题
一天, 你打算为游客们创建一款导游程序。 该程序的核心功能是提供美观的地图, 以帮助用户在任何城市中快速定位。
用户期待的程序新功能是自动路线规划: 他们希望输入地址后就能在地图上看到前往目的地的最快路线。
程序的首个版本只能规划公路路线。 驾车旅行的人们对此非常满意。 但很显然, 并非所有人都会在度假时开车。 因此你在下次更新时添加了规划步行路线的功能。 此后, 你又添加了规划公共交通路线的功能。
而这只是个开始。 不久后, 你又要为骑行者规划路线。 又过了一段时间, 你又要为游览城市中的所有景点规划路线。
如下图:导游代码将变得非常臃肿

尽管从商业角度来看, 这款应用非常成功, 但其技术部分却让你非常头疼: 每次添加新的路线规划算法后, 导游应用中主要类的体积就会增加一倍。 终于在某个时候, 你觉得自己没法继续维护这堆代码了。
无论是修复简单缺陷还是微调街道权重, 对某个算法进行任何修改都会影响整个类, 从而增加在已有正常运行代码中引入错误的风险。
此外, 团队合作将变得低效。 如果你在应用成功发布后招募了团队成员, 他们会抱怨在合并冲突的工作上花费了太多时间。 在实现新功能的过程中, 你的团队需要修改同一个巨大的类, 这样他们所编写的代码相互之间就可能会出现冲突。
二、解决方案
策略模式包括如下要素:
- 一组类的实现:首先找到一组不同的策略,分别实现为对应的策略类。
- 实现接口:然后使这些类实现相同的接口。
- 上下文类:用成员变量,存储接口(通过接口即存储了每种策略类的视线)。上下文类不执行任务,而是委托给策略对象执行。
上下文类不负责选择符合任务需要的算法—客户端会将所需的策略传递给上下文。实际上,上下文并不是很了解策略,它通过同样的接口方法和所有策略做交互(二该接口只需要暴露一个方法来触发所选策略中封装的算法即可)。
因此,上下文可独立于具体策略。这样就可以在不修改上下文代码和其他策略的情况下,添加新算法或修改已有算法了。

示例:如上图
- 有 RoadStrategy、Walking Strategy、PublicTransportStrategy 三种策略类。
- 他们都实现了 RouteStrategy interface。
- Navigator 上下文类持有 routeStrategy 成员变量。
- 当用户调用 Navigator.buildRoute() 方法时,其会调用其成员变量 routeStrategy.buildRoute() 方法。
在上图的导游应用中,每个路线规划算法,都可以被抽取到只有一个 buildRoute() 方法的独立类中,该方法接收起点和终点作为参数,并返回路线中途点的集合。
即使传递给每个路径规划类的参数一模一样, 其所创建的路线也可能完全不同。 主要导游类的主要工作是在地图上渲染一系列中途点, 不会在意如何选择算法。 该类中还有一个用于切换当前路径规划策略的方法(就像高德地图一样), 因此客户端 (例如用户界面中的按钮) 可用其他策略替换当前选择的路径规划行为。
2.1 真实世界的类比

假如你需要前往机场。 你可以选择乘坐公共汽车、 预约出租车或骑自行车。 这些就是你的出行策略。 你可以根据预算或时间等因素来选择其中一种策略。
2.2 策略模式结构

2.3 适用场景
-
当你想使用对象中各种不同的算法变体, 并希望能在运行时切换算法时, 可使用策略模式。
策略模式让你能够将对象关联至可以不同方式执行特定子任务的不同子对象, 从而以间接方式在运行时更改对象行为。 -
当你有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式。
策略模式让你能将不同行为抽取到一个独立类层次结构中, 并将原始类组合成同一个, 从而减少重复代码。 -
如果算法在上下文的逻辑中不是特别重要, 使用该模式能将类的业务逻辑与其算法实现细节隔离开来。
策略模式让你能将各种算法的代码、 内部数据和依赖关系与其他代码隔离开来。 不同客户端可通过一个简单接口执行算法, 并能在运行时进行切换。 -
当类中使用了复杂条件运算符以在同一算法的不同变体中切换时, 可使用该模式。
策略模式将所有继承自同样接口的算法抽取到独立类中, 因此不再需要条件语句。 原始对象并不实现所有算法的变体, 而是将执行工作委派给其中的一个独立算法对象。
2.4 实现方式
- 从上下文类中找出修改频率较高的算法 (也可能是用于在运行时选择某个算法变体的复杂条件运算符)。
- 声明该算法所有变体的通用策略接口。
- 将算法逐一抽取到各自的类中, 它们都必须实现策略接口。
- 在上下文类中添加一个成员变量用于保存对于策略对象的引用。 然后提供设置器以修改该成员变量。 上下文仅可通过策略接口同策略对象进行交互, 如有需要还可定义一个接口来让策略访问其数据。
- 客户端必须将上下文类与相应策略进行关联, 使上下文可以预期的方式完成其主要工作。
2.5 优缺点

2.6 与其他模式的关系
- 桥接模式、 状态模式和策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。
- 命令模式和策略看上去很像, 因为两者都能通过某些行为来参数化对象。 但是, 它们的意图有非常大的不同。
- 你可以使用命令来将任何操作转换为对象。 操作的参数将成为对象的成员变量。 你可以通过转换来延迟操作的执行、 将操作放入队列、 保存历史命令或者向远程服务发送命令等。
- 另一方面, 策略通常可用于描述完成某件事的不同方式, 让你能够在同一个上下文类中切换算法。
- 装饰模式可让你更改对象的外表, 策略则让你能够改变其本质。
- 模板方法模式基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 策略基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。
- 状态可被视为策略的扩展。 两者都基于组合机制: 它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。 策略使得这些对象相互之间完全独立, 它们不知道其他对象的存在。 但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态。
三、示例代码
3.1 go
https://refactoringguru.cn/design-patterns/strategy/go/example
package mainimport "fmt"// interface
type EvictionAlgo interface {evict(c *Cache)
}// 策略类
type Fifo struct {
}func (l *Fifo) evict(c *Cache) {fmt.Println("Evicting by fifo strategy")
}type Lru struct {
}func (l *Lru) evict(c *Cache) {fmt.Println("Evicting by lru strategy")
}type Lfu struct {
}func (l *Lfu) evict(c *Cache) {fmt.Println("Evicting by lfu strategy")
}// 上下文类
type Cache struct {storage map[string]stringevictionAlgo EvictionAlgo // 策略接口capacity intmaxCapacity int
}func initCache(e EvictionAlgo) *Cache {storage := make(map[string]string)return &Cache{storage: storage,evictionAlgo: e,capacity: 0,maxCapacity: 2,}
}// 变更策略实现
func (c *Cache) setEvictionAlgo(e EvictionAlgo) {c.evictionAlgo = e
}func (c *Cache) add(key, value string) {if c.capacity == c.maxCapacity {c.evict()}c.capacity++c.storage[key] = value // 业务逻辑是添加到 storage,并维护 capacity 不超过 maxCapacity
}func (c *Cache) get(key string) {delete(c.storage, key)
}func (c *Cache) evict() {c.evictionAlgo.evict(c)c.capacity--
}// 客户端
func main() {lfu := &Lfu{}cache := initCache(lfu)cache.add("a", "1")cache.add("b", "2")cache.add("c", "3")lru := &Lru{}cache.setEvictionAlgo(lru)cache.add("d", "4")fifo := &Fifo{}cache.setEvictionAlgo(fifo)cache.add("e", "5")
}// code result:
Evicting by LRU
Evicting by LFU
Evicting by FIFO
3.2 rust
trait RouteStrategy {fn build_route(&self, from: &str, to: &str);
}struct WalkingStrategy;
impl RouteStrategy for WalkingStrategy {fn build_route(&self, from: &str, to: &str) {println!("Walking from {} to {}", from, to)}
}struct BikingStrategy;
impl RouteStrategy for BikingStrategy {fn build_route(&self, from: &str, to: &str) {println!("Biking from {} to {}", from, to)}
}struct Navigator<T: RouteStrategy> {strategy: T,
}impl<T: RouteStrategy> Navigator<T> {pub fn new(route_strategy: T) -> Self {Navigator {strategy: route_strategy,}}pub fn route(&self, from: &str, to: &str) {self.strategy.build_route(from, to)}
}fn main() {let navigator = Navigator::new(WalkingStrategy);navigator.route("Home", "Club");navigator.route("Club", "Work");let navigator = Navigator::new(BikingStrategy);navigator.route("Home", "Club");navigator.route("Club", "Work");
}
相关文章:
【设计模式】4、策略模式
文章目录 一、问题二、解决方案2.1 真实世界的类比2.2 策略模式结构2.3 适用场景2.4 实现方式2.5 优缺点2.6 与其他模式的关系 三、示例代码3.1 go3.2 rust 策略模式是一种行为设计模式,它能定义一系列算法,把每种算法分别放入独立的类中,以是…...
【C++学习手札】多态:掌握面向对象编程的动态绑定与继承机制(深入)
🎬慕斯主页:修仙—别有洞天 ♈️今日夜电波:世界上的另一个我 1:02━━━━━━️💟──────── 3:58 🔄 ◀️ ⏸ ▶️ ☰ &am…...
【机构vip教程】Android SDK手机测试环境搭建
Android SDK 的安装和环境变量的配置 前置条件:需已安装 jdk1.8及 以上版本 1、下载Android SDK,解压后即可(全英文路径);下载地址:http://tools.android-studio.org/index.php/sdk 2、新建一个环境变量&…...
2024.2.18
使用fgets统计给定文件的行数 #include<stdio.h> #include<string.h> int main(int argc, const char *argv[]) {FILE *fpNULL;if((fpfopen("./test.txt","w"))NULL){perror("open err");return -1;}fputc(h,fp);fputc(\n,fp);fput…...
Haproxy实验
环境: servera(Haproxy):192.168.233.132 serverb(web1):192.168.233.144 serverc(web2):192.168.233.140 serverd(客户端):192.168.233.141 servera(Haproxy): yum install haproxy -y vim /etc/haproxy/haproxy.cfg(配置文件) # 设置日志&#…...
CSRNET图像修复,DNN
CSRNET图像修复 CSRNET图像修复,只需要OPENCV的DNN...
004 - Hugo, 分类
004 - Hugo, 分类content文件夹 004 - Hugo, 分类 content文件夹 ├─.obsidian ├─categories │ ├─Python │ └─Test ├─page │ ├─about │ ├─archives │ ├─links │ └─search └─post├─chinese-test├─emoji-support├─Git教程├─Hugo分类├─…...
Vue3之ElementPlus中Table选中数据的获取与清空方法
Vue3之ElementPlus中Table选中数据的获取与清空方法 文章目录 Vue3之ElementPlus中Table选中数据的获取与清空方法1. 点击按钮获取与清空选中表格的数据1. 用到ElementPlus中Table的两个方法2. 业务场景3. 操作案例 1. 点击按钮获取与清空选中表格的数据 1. 用到ElementPlus中…...
Leetcode 516.最长回文子序列
题意理解: 给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。 子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。 回文理解为元素对称的字串,这里…...
cool Node后端 中实现中间件的书写
1.需求 在node后端中,想实现一个专门鉴权的文件配置,可以这样来解释 就是 有些接口需要token调用接口,有些接口不需要使用token 调用 这期来详细说明一下 什么是中间件中间件顾名思义是指在请求和响应中间,进行请求数据的拦截处理…...
Leecode之面试题消失的数字
一.题目及剖析 https://leetcode.cn/problems/missing-number-lcci/description/ 数组nums包含从0到n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗? 注意:本题相对书上原题稍作改动 示例 1&…...
STM32的三种下载方式
结果jlink,串口,stlink方式都没有问题,是当时缩减代码,看真正起作用的代码段有哪些,就把GPIO初始化中 /*开启GPIO外部时钟*/RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE); 把开启外部时钟的代码注释掉了。…...
华为 huawei 交换机 接口 MAC 地址学习限制接入用户数量 配置示例
目录 组网需求: 配置思路: 操作步骤: 配置文件: 组网需求: 如 图 2-14 所示,用户网络 1 和用户网络 2 通过 LSW 与 Switch 相连, Switch 连接 LSW 的接口为GE0/0/1 。用户网络 1 和用户网络 2 分别属于 VLAN10 和 V…...
使用Python生成二维码的完整指南
无边落木萧萧下,不如跟着可莉一起游~ 可莉将这篇博客收录在了:《Python》 可莉推荐的优质博主首页:Kevin ’ s blog 本文将介绍如何使用Python中的qrcode库来生成二维码。通过简单的代码示例和详细解释,读者将学习如何在Python中轻…...
排序前言冒泡排序
目录 排序应用 常见的排序算法 BubbleSort冒泡排序 整体思路 图解分析 代码实现 每趟 写法1 写法2 代码NO1 代码NO2优化 时间复杂度 排序概念 排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递…...
红队笔记Day3-->隧道上线不出网机器
昨天讲了通过代理的形式(端口转发)实现了上线不出网的机器,那么今天就来讲一下如何通过隧道上线不出网机器 目录 1.网络拓扑 2.开始做隧道?No!!! 3.icmp隧道 4.HTTP隧道 5.SSH隧道 1.什么…...
C 练习实例70-求字符串长度
题目:写一个函数,求一个字符串的长度,在 main 函数中输入字符串,并输出其长度。 解答: #include <stdio.h> int length(char *s); int main() {int len;char str[20];printf("请输入字符串:\n");scan…...
HarmonyOS—@State装饰器:组件内状态
State装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就和自定义组件的渲染绑定起来。当状态改变时,UI会发生对应的渲染改变。 在状态变量相关装饰器中,State是最基础的,使变量拥有状态属性的装饰器&a…...
Linux系统——防火墙拓展及重点理解
目录 一、iptables 1.基本语法 2.四表五链——重点记忆 2.1四表 2.2五链 2.3总结 3.iptables选项示例 3.1 -Z 清空流量计数 3.2 -P 修改默认规则 3.3 -D 删除规则 3.4 -R 指定编号替换规则 5.白名单 6.通用匹配 7.示例 7.1添加回环网卡 7.2可以访问端口 7.3 主…...
阿里云短信验证码的两个坑
其它都参照官网即可,其中有两个坑需要注意: 1、除去官网pom引用的包之外,还需要引用以下包: <dependency><groupId>org.apache.httpcomponents.client5</groupId><artifactId>httpclient5</artifact…...
Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
