【设计模式】 策略模式介绍及C代码实现
【设计模式】 策略模式介绍及C代码实现
背景
在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂,而且有时候支持不使用的算法也是一个性能负担。 如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
我们还是使用这个例子,比如你要从北京去上海出差,出差的工作是不变的,但是每次去使用的交通工具却有不同的方式,可能有火车、可能飞机、可能开车。如果写程序实现,我们就可以分别将不同的交通工具方式写到不同的类中,然后再代码运行时根据不同的交通方式调用不同类的对象,从而实现在一套代码中通过运行是的调用兼容不同交通工具方式的出差这个场景。 这种定义一系列可互相替换的算法,并且每个算法独立封装,然后再运行的时候动态替换的方式就是策略模式。
定义
策略模式(Strategy Pattern)是一种常用的面向对象设计模式,它定义了一系列可互相替换的算法或策略,并将每个算法封装成独立的对象,使得它们可以在运行时动态地替换。
具体来说,策略模式定义了一系列算法,每个算法都封装在一个具体的策略类中,这些策略类实现了相同的接口或抽象类。在使用算法的时候,客户端通过一个上下文对象来调用策略类的方法,从而完成算法的执行。这样,客户端可以在运行时动态地选择不同的策略类,从而实现不同的行为。
策略模式通常由三个角色组成:
-
环境角色(Context):它是客户端代码所定义的一组接口或抽象类,表示一种算法或策略。
-
抽象策略角色(Strategy):它是一个抽象类或接口,定义了一组算法或策略的接口。
-
具体策略角色(Concrete Strategy):它是抽象策略的具体实现类,实现了策略角色定义的算法或策略。
应用场景
策略模式主要应用在以下场景:
- 当你想使用对象中各种不同的算法变体, 并希望能在运行时切换算法时, 可使用策略模式。
策略模式让你能够将对象关联至可以不同方式执行特定子任务的不同子对象, 从而以间接方式在运行时更改对象行为。
- 当你有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式。
策略模式让你能将不同行为抽取到一个独立类层次结构中, 并将原始类组合成同一个, 从而减少重复代码。
- 如果算法在上下文的逻辑中不是特别重要, 使用该模式能将类的业务逻辑与其算法实现细节隔离开来。
策略模式让你能将各种算法的代码、 内部数据和依赖关系与其他代码隔离开来。 不同客户端可通过一个简单接口执行算法, 并能在运行时进行切换。
-
当类中使用了复杂条件运算符以在同一算法的不同变体中切换时, 可使用该模式。
策略模式将所有继承自同样接口的算法抽取到独立类中, 因此不再需要条件语句。 原始对象并不实现所有算法的变体, 而是将执行工作委派给其中的一个独立算法对象。
具体的应用场景包括:
-
订单价格计算:根据不同的促销活动(如会员折扣、团购活动、满减优惠等),可以使用不同的算法来计算订单的价格。
-
支付方式选择:根据不同的支付方式(如信用卡、支付宝、微信支付等),可以使用不同的算法来完成支付过程。
-
排序算法选择:根据不同的排序算法(如冒泡排序、快速排序、归并排序等),可以使用不同的算法来排序。
-
表单验证:根据不同的表单验证规则(如必填字段、长度限制、格式要求等),可以使用不同的算法来验证表单数据的有效性。
在这些场景中,策略模式可以帮助我们将算法或策略的实现与算法或策略的使用分离开来,从而实现代码的复用、可维护性和灵活性。
模式结构

实现步骤
策略模式设的主要实现步骤:
-
定义一个策略接口或抽象类,该接口或抽象类声明了策略所需的方法,具体的策略类将实现这些方法。
-
定义具体的策略类,实现策略接口或抽象类中声明的方法。
-
定义一个上下文类,该类包含一个策略对象的引用,用于调用策略对象的方法。上下文类也可以定义一些辅助方法,用于支持策略的使用。
-
在客户端中,根据需要创建不同的策略对象,并将其传递给上下文对象。
-
在上下文对象中调用策略对象的方法,完成具体的算法或行为。
C语言代码示例
其实设计模式是一种与编程语言无关的设计思想,但是其中很重要的思想就是面向对象,所以在面向对象的语言,比如C++、Java中实现起来就非常顺手,但因为我本人是C语言出身,并且作为主要编程语言的,所以就使用了C语言来实现模板方法的设计模式。
在C语言中,由于没有面向对象的特性,策略模式的实现方式会略有不同。通常可以使用函数指针来模拟接口,使用函数指针变量来引用具体的策略函数。下面是一个简单的示例,演示了如何使用C语言实现策略模式:
#include <stdio.h>// 1. 定义函数指针类型
typedef void (*Strategy)(int, int);// 2. 定义具体的策略函数
void operation_add(int num1, int num2) {printf("%d + %d = %d\n", num1, num2, num1 + num2);
}void operation_subtract(int num1, int num2) {printf("%d - %d = %d\n", num1, num2, num1 - num2);
}void operation_multiply(int num1, int num2) {printf("%d * %d = %d\n", num1, num2, num1 * num2);
}// 3. 定义上下文结构体
typedef struct {Strategy strategy;
} Context;// 4. 在客户端中使用策略模式
int main() {Context context;// 使用加法策略context.strategy = operation_add;context.strategy(10, 5);// 使用减法策略context.strategy = operation_subtract;context.strategy(10, 5);// 使用乘法策略context.strategy = operation_multiply;context.strategy(10, 5);return 0;
}
在上面的示例中,我们首先定义了一个函数指针类型 Strategy,该函数指针类型接受两个整型参数,并且没有返回值。接着定义了具体的策略函数 operation_add、operation_subtract 和 operation_multiply,这些函数都符合 Strategy 函数指针类型的定义。然后定义了上下文结构体 Context,该结构体包含一个函数指针类型的成员变量 strategy。最后,在 main 函数中创建一个上下文对象 context,并分别将不同的策略函数赋值给 context.strategy 成员变量,最终调用 context.strategy 函数指针来执行具体的策略函数。
需要注意的是,由于C语言没有面向对象的特性,策略模式的实现方式相对于其他编程语言会更加笨拙,代码可读性也不太好。如果需要实现更复杂的策略模式,建议使用其他面向对象的编程语言来实现。
总结
策略模式为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。并且策略模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要策略模式。如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。
下面我们从设计原则的角度分析策略模式:
- 开闭原则:策略模式可以在不修改原有代码的情况下添加、删除、切换不同的策略,因此可以有效地满足开闭原则。
- 代码复用:不同的策略可以复用相同的代码,减少了代码冗余。
- 单一职责原则:将不同的算法封装在不同的策略中,实现了单一职责原则。
策略模式的主要优点在于:
-
提高了代码的复用性:将算法封装在独立的策略类中,可以避免代码重复,提高了代码的复用性。
-
易于扩展和维护:由于算法的实现与算法的使用分离开来,所以在添加新的算法或修改现有算法时,不会影响到其他算法的实现和使用,易于扩展和维护。
-
提高了代码的灵活性:在运行时动态地选择不同的策略类,可以实现不同的行为,提高了代码的灵活性。
总之,策略模式是一种简单而有效的设计模式,可以帮助我们提高代码的复用性、可维护性和灵活性。
相关文章:
【设计模式】 策略模式介绍及C代码实现
【设计模式】 策略模式介绍及C代码实现 背景 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂,而且有时候支持不使用的算法也是一个性能负担。 如何…...
【数据库】第二章 关系数据库
第二章 关系数据库 2.1关系数据结构及形式化定义 关系 域(domain) :域是一组具有相同数据类型的值的集合,可以取值的个数叫基数 笛卡尔积 :一个记录叫做一个元组(tuple),元组中每一个属性值,叫一个分量 基数&…...
oracle和mysql的分页
oracle的分页:rownum 注意:: 对 ROWNUM 只能使用 < 或 <, 用 、 >、 > 都不能返回任何数据。 rownum是对结果集的编序排列,始终是从1开始,所以rownum直接使用时不允许使用>、> 所以当查询中间部分的信息时&…...
深拷贝与浅拷贝的理解
浅拷贝的理解浅拷贝的话只会拷贝基本数据类型,例如像string、Number等这些,类似:Object、Array 这类的话拷贝的就是对象的一个指针(通俗来讲就是拷贝一个引用地址,指向的是一个内存同一份数据),也就是说当拷贝的对象数…...
Shell变量
一、变量分类 根据作用域分三种 (一)只在函数内有效,叫局部变量 (二)只在当前shell进程中有效,叫做全局变量 (三)在当前shell进程与子进程中都有效,叫做环境变量 shell进…...
Android 8请求权限时弹窗BUG
弹窗BUG 应用使用requestPermissions申请权限时,系统会弹出一个选择窗口,可进行允许或拒绝, 此窗口中有一个”不再询问“的选择框, ”拒绝”及“允许”的按钮。 遇到一个Bug,单点击“不再询问”,“允许”这个按钮会变…...
路漫漫:网络空间的监管趋势
网络空间是“以相互依存的网络基础设施为基本架构,以代码、信息与数据的流动为环境,人类利用信息通讯技术与应用开展活动,并与其他空间高度融合与互动的空间”。随着信息化技术的发展,网络空间日益演绎成为与现实人类生存空间并存…...
洛谷 P1208 [USACO1.3]混合牛奶 Mixing Milk
最后水一篇水题题解(实在太水了) # [USACO1.3]混合牛奶 Mixing Milk ## 题目描述 由于乳制品产业利润很低,所以降低原材料(牛奶)价格就变得十分重要。帮助 Marry 乳业找到最优的牛奶采购方案。 Marry 乳业从一些奶农手…...
数据库的基本查询
注意:LIMIT的两个参数,第一个是起始位置,第二个是一次查询到多少页。注意:什么类型的数字都是可以排序的。日期的降序是从现在到以前,MySQL ENUM值如何排序?在MYSQL中,我们知道每个ENUM值都与一…...
10 分钟把你的 Web 应用转为桌面端应用
在桌面端应用上,Electron 也早已做大做强,GitHub桌面端、VSCode、Figma、Notion、飞书、剪映、得物都基于此。但最近后起之秀的 Tauri 也引人注目,它解决了 Electron 一个大的痛点——打包产物特别大。 我们知道 Electron 基于谷歌内核 Chro…...
Delphi RSA加解密(二)
dll开发环境: Delphi XE 10.1 Berlin exe开发环境: Delphi 6 前提文章: Delphi RSA加解密(一) 目录 1. 概述 2. 准备工作 2.1 下载DEMO程序 2.2 字符编码说明 3. Cryption.dll封装 3.1 接口概况 3.2 uPub.pas单元代码 3.3 uInterface.pas单元代码 3.4 特别注意 4. 主程序…...
pytorch 深度学习早停设置
当你设置早停的时候你需要注意的是你可能得在几个epoch后才开始判断早停。 早停参数设置 早停(Early Stopping)是一种常用的防止深度学习模型过拟合的方法。早停的设置需要根据具体情况进行调整,常见的做法是在模型训练过程中使用验证集&am…...
【Vue学习】Vue高级特性
1. 自定义v-model Vue中的自定义v-model指的是在自定义组件中使用v-model语法糖来实现双向绑定。在Vue中,通过v-model指令可以将表单元素的值与组件实例的数据进行双向绑定。但是对于自定义组件,如果要实现v-model的双向绑定,就需要自定义v-…...
Android 12.0 系统Settings去掉开发者模式功能
1.概述 在12.0的系统rom产品定制化开发中,在系统Settings中的关于手机的选项中,系统默认点击版本号5次会自动打开开发者模式,但是在某些产品开发过程中,禁止打开开发者模式,需要去掉开发者模式的功能,所以需要在系统Settings中查看开发者模式的相关流程代码,然后禁用掉开…...
buu [NCTF2019]babyRSA 1
题目描述: 题目分析: 首先明确两个公式: e*d 1 mod (p-1)(q-1) ed1 e*d - 1 k(p-1)(q-1)想要解出此题,我们必须知道n,而要知道n,我们要知道p和q的值通过 e*d 的计算,我们知道其长度为2066位,而生成p的…...
Java:如何选择一个Java API框架
Java编程语言是一种高级的、面向对象的语言,它使开发人员能够创建健壮的、可重用的代码。Java以其可移植性和平台独立性而闻名,这意味着Java代码可以在任何支持Java运行时环境(JRE)的系统上运行。Java和Node js一样,是一种功能强大的通用编程…...
mt6735 MIC 音量的调整及原理介绍
[DESCRIPTION] MIC 音量的调整及原理介绍[SOLUTION] audio_ver1_volume_custom_default.h#define VER1_AUD_VOLUME_MIC \ 64,112,192,144,192,192,184,184,184,184,184,0,0,0,0,\ 255,192,192,180,192,192,196,184,184,184,184,0,0,0,0,\ 255,208,208,180,255,208,196,0,0,0,0,…...
【深度学习】什么是线性回归逻辑回归单层神经元的缺陷
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录逻辑回归&线性回归单层神经元的缺陷单层神经元的缺陷逻辑回归&线性回归 线性回归预测的是一个连续值, 逻辑回归给出的”是”和“否”的回答. 等…...
Spring拦截器
SpringMVC提供了拦截器机制,允许运行目标方法之前进行一些拦截工作或者目标方法运行之后进行一下其他相关的处理。自定义的拦截器必须实现HandlerInterceptor接口。preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求…...
8个可能降低网站搜索引擎信任度的错误
如果觉得文章对你有用请点赞与关注,每一份支持都是我坚持更新更优质内容的动力!!!例如,发布一段质量差的网站内容不会完全破坏您的排名机会,只要您的内容策略的其余部分井井有条。但是本地SEO中存在一些错误…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...
