【设计模式】 策略模式介绍及C代码实现

news2024/12/21 21:54:23

【设计模式】 策略模式介绍及C代码实现

背景

  在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂,而且有时候支持不使用的算法也是一个性能负担。 如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
   我们还是使用这个例子,比如你要从北京去上海出差,出差的工作是不变的,但是每次去使用的交通工具却有不同的方式,可能有火车、可能飞机、可能开车。如果写程序实现,我们就可以分别将不同的交通工具方式写到不同的类中,然后再代码运行时根据不同的交通方式调用不同类的对象,从而实现在一套代码中通过运行是的调用兼容不同交通工具方式的出差这个场景。 这种定义一系列可互相替换的算法,并且每个算法独立封装,然后再运行的时候动态替换的方式就是策略模式。

定义

  策略模式(Strategy Pattern)是一种常用的面向对象设计模式,它定义了一系列可互相替换的算法或策略,并将每个算法封装成独立的对象,使得它们可以在运行时动态地替换。

  具体来说,策略模式定义了一系列算法,每个算法都封装在一个具体的策略类中,这些策略类实现了相同的接口或抽象类。在使用算法的时候,客户端通过一个上下文对象来调用策略类的方法,从而完成算法的执行。这样,客户端可以在运行时动态地选择不同的策略类,从而实现不同的行为。

策略模式通常由三个角色组成:

  • 环境角色(Context):它是客户端代码所定义的一组接口或抽象类,表示一种算法或策略。

  • 抽象策略角色(Strategy):它是一个抽象类或接口,定义了一组算法或策略的接口。

  • 具体策略角色(Concrete Strategy):它是抽象策略的具体实现类,实现了策略角色定义的算法或策略。

应用场景

策略模式主要应用在以下场景:

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

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

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

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

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

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

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

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

具体的应用场景包括:

  1. 订单价格计算:根据不同的促销活动(如会员折扣、团购活动、满减优惠等),可以使用不同的算法来计算订单的价格。

  2. 支付方式选择:根据不同的支付方式(如信用卡、支付宝、微信支付等),可以使用不同的算法来完成支付过程。

  3. 排序算法选择:根据不同的排序算法(如冒泡排序、快速排序、归并排序等),可以使用不同的算法来排序。

  4. 表单验证:根据不同的表单验证规则(如必填字段、长度限制、格式要求等),可以使用不同的算法来验证表单数据的有效性。

  在这些场景中,策略模式可以帮助我们将算法或策略的实现与算法或策略的使用分离开来,从而实现代码的复用、可维护性和灵活性。

模式结构

在这里插入图片描述

实现步骤

策略模式设的主要实现步骤:

  1. 定义一个策略接口或抽象类,该接口或抽象类声明了策略所需的方法,具体的策略类将实现这些方法。

  2. 定义具体的策略类,实现策略接口或抽象类中声明的方法。

  3. 定义一个上下文类,该类包含一个策略对象的引用,用于调用策略对象的方法。上下文类也可以定义一些辅助方法,用于支持策略的使用。

  4. 在客户端中,根据需要创建不同的策略对象,并将其传递给上下文对象。

  5. 在上下文对象中调用策略对象的方法,完成具体的算法或行为。

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对象,从而节省对象开销。

下面我们从设计原则的角度分析策略模式:

  • 开闭原则:策略模式可以在不修改原有代码的情况下添加、删除、切换不同的策略,因此可以有效地满足开闭原则。
  • 代码复用:不同的策略可以复用相同的代码,减少了代码冗余。
  • 单一职责原则:将不同的算法封装在不同的策略中,实现了单一职责原则。

策略模式的主要优点在于:

  1. 提高了代码的复用性:将算法封装在独立的策略类中,可以避免代码重复,提高了代码的复用性。

  2. 易于扩展和维护:由于算法的实现与算法的使用分离开来,所以在添加新的算法或修改现有算法时,不会影响到其他算法的实现和使用,易于扩展和维护。

  3. 提高了代码的灵活性:在运行时动态地选择不同的策略类,可以实现不同的行为,提高了代码的灵活性。

总之,策略模式是一种简单而有效的设计模式,可以帮助我们提高代码的复用性、可维护性和灵活性。

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

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

相关文章

go单元测试

接着上一篇中的go module创建项目calc为例&#xff0c;在simplemath包中&#xff0c;是使用在命令行中使用交互式的方式进行测试&#xff0c;现在可以为这几个函数实现单元测试&#xff0c; go test&#xff0c;这个测试工具来自于 Go 官方的 gc 工具链。 运行 go test 命令将执…

JVM本地方法接口和本地方法栈

1、本地方法概述简单地讲&#xff0c;一个Native Method是一个Java调用非Java代码的接囗。一个Native Method是的实现由非Java语言实现&#xff0c;比如C。这个特征并非Java所特有&#xff0c;很多其它的编程语言都有这一机制&#xff0c;比如在C中&#xff0c;你可以用extern …

openpnp - 零碎记录

文章目录openpnp - 零碎记录概述笔记配置文件保存无效ENDopenpnp - 零碎记录 概述 这段时间, 正在配置校准手头的openpnp设备, 用的官网最新的openpnp2.0. 由于openpnp的bug和自己的不细致, 导致多次校准失败. 现在从头校准时, 每进行一步, 就保存一下配置文件, 如果最终发现…

MySQL_主从复制读写分离

主从复制 概述 主从复制是指将主数据库的DDL和DML操作通过二进制日志传到从库服务器中&#xff0c;然后在从库上对这些日志重新执行&#xff08;也叫重做&#xff09;&#xff0c;从而使得从库和主库的数据保持同步。 MySQL支持一台主库同时向多台从库进行复制&#xff0c;从…

leetcode 31~40 学习经历

leetcode 31~40 学习经历31. 下一个排列32. 最长有效括号33. 搜索旋转排序数组34. 在排序数组中查找元素的第一个和最后一个位置35. 搜索插入位置36. 有效的数独37. 解数独38. 外观数列39. 组合总和40. 组合总和 II小结31. 下一个排列 整数数组的一个 排列 就是将其所有成员以序…

3.JVM内存分配机制详解【2023】

redis跳表 内容概要 内存分配 1.类加载检查 &#x1f60a;虚拟机遇到一条new指令时&#xff0c;首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用&#xff0c;并且检查这个 符号引用代表的类是否已被加载、解析和初始化过。如果没有&#xff0c;那必须先执…

MySQL/Oracle获取当前时间几天/分钟前的时间

获取当前时间 要想获取当前时间几天/分钟前的时间&#xff0c;首先要知道怎么获取当前时间&#xff1b; 对于MySQL和Oracle获取当前时间的方法是不一样的&#xff1b; MySQL&#xff1a; select NOW(); 示例&#xff1a; Oracle&#xff1a; select sysdate from dual; 示…

西北工业大学2020-2021学年大物(I)下期末试题选填解析

2 位移电流。磁效应服从安培环路&#xff0c;热效应不服从焦耳-楞次定律。注意&#xff0c;它是变化的电场而非磁场产生。3 又考恒定磁场中安培环路定理。4感生电场5 麦克斯韦速率分布函数。6 相同的高温热源和低温热源之间的一切可逆热机的工作效率相等&#xff0c;无论工质如…

java 内部类的四种“写法”

基本介绍语法格式分类成员内部类静态内部类局部内部类匿名内部类&#xff08;&#x1f402;&#x1f58a;&#xff09;一、基本介绍 : 1.概述当一个类的内部又完整地嵌套了另一个类时&#xff0c;被嵌套于内部的“内核”我们称之为“内部类”(inner class)&#xff1b;而包含该…

Airbyte,数据集成的未来

Gartner 曾预计&#xff0c;到 2025 年&#xff0c;80% 寻求扩展数字业务的组织将失败。因为他们没有采用现代方法来进行数据和分析治理。数据生态是基础架构生态的最重要一环&#xff0c;数据的处理分发与计算&#xff0c;从始至终贯穿了整个数据流通生态。自从数据集中在数据…

3. Unity之三维模型

1. 网格 Mesh 三维物体模型在unity中一般称为mesh&#xff0c;即网格数据&#xff0c;模型一般使用专用的建模软件设计&#xff0c;将mesh文件导入到unity中进行使用&#xff0c;一般mesh中保存的是三维模型的面和顶点数据。在unity中通过下图方法进行调整&#xff0c;其中&am…

MakeFile教程

前言 当我们需要编译一个比较大的项目时&#xff0c;编译命令会变得越来越复杂&#xff0c;需要编译的文件越来越多。其 次就是项目中并不是每一次编译都需要把所有文件都重新编译&#xff0c;比如没有被修改过的文件则不需要重 新编译。工程管理器就帮助我们来优化这两个问题…

Elasticsearch7.8.0版本进阶——IK中文分词器

目录一、ES 的默认分词器测试示例二、IK 中文分词器2.1、IK 中文分词器下载地址2.2、ES 引入IK 中文分词器2.3、IK 中文分词器测试示例三、ES 扩展词汇测试示例一、ES 的默认分词器测试示例 通过 Postman 发送 GET 请求查询分词效果&#xff0c;在消息体里&#xff0c;指定要分…

python社团 培训记录(自2023年2月24日始)

在单位开设了Python社团&#xff0c;在此记录上课的有关情况&#xff1a; 课程概述&#xff1a;本社团主要针对五、六年级&#xff0c;初始招生&#xff08;上课前&#xff09;28人&#xff08;五、六年级各14人&#xff09;&#xff0c;后&#xff08;上课时&#xff09;人员…

一文让你彻底理解Linux内核调度器进程优先级

一、前言 本文主要描述的是进程优先级这个概念。从用户空间来看&#xff0c;进程优先级就是nice value和scheduling priority&#xff0c;对应到内核&#xff0c;有静态优先级、realtime优先级、归一化优先级和动态优先级等概念。我们希望能在第二章将这些相关的概念描述清楚。…

优秀的网络安全工程师应该有哪些能力?

网络安全工程师是一个各行各业都需要的职业&#xff0c;工作内容属性决定了它不会只在某一方面专精&#xff0c;需要掌握网络维护、设计、部署、运维、网络安全等技能。目前稍有经验的薪资在10K-30K之间&#xff0c;全国的网络安全工程师还处于一个供不应求的状态&#xff0c;因…

Linux | 项目自动化构建工具 - make/Makefile

make / Makefile一、前言二、make/Makefile背景介绍1、Makefile是干什么的&#xff1f;2、make又是什么&#xff1f;三、demo实现【见见猪跑&#x1f416;】三、依赖关系与依赖方法1、概念理清2、感性理解【父与子】3、深层理解【程序的翻译环境 栈的原理】四、多学一招&#…

网络编程(Java)

网络协议通信 IP和端口号 要想使计算机能够通信&#xff0c;必需为每台计算机指定一个标识号&#xff0c;通过这个标识号指定接受数据的计算机或者发送数据的计算机。一般的&#xff0c;IP地址就是一个计算机的标识号&#xff0c;它可以唯一标识一台计算机。 IP地址由两部分组…

AUTOSAR 自适应平台

总目录链接>> AutoSAR入门和实战系列总目录 文章目录AUTOSAR 自适应平台动机标准自适应平台基础基本功能通信安全保障自适应平台服务DemonstratorDemonstrator实现路线图本系列文章由两部分组成&#xff1a;第一部分讨论了AUTOSAR 经典平台&#xff0c;该平台旨在基于微…

Linux下的进程地址空间

Linux下的进程地址空间程序地址空间回顾从代码结果推结论引入进程地址空间页表为什么要有进程地址空间重新理解进程地址空间程序地址空间回顾 我们在初学C/C的时候&#xff0c;我们会经常看见老师们画这样的内存布局图&#xff1a; 可是这真的是内存吗&#xff1f; 如果不是它…