当前位置: 首页 > news >正文

【Dart 教程系列第 49 篇】什么是策略设计模式?如何在 Dart 中使用策略设计模式

这是【Dart 教程系列第 49 篇】,如果觉得有用的话,欢迎关注专栏。

博文当前所用 Flutter SDK:3.22.1、Dart SDK:3.4.1

文章目录

      • 一:什么是策略设计模式?
      • 二:为什么要使用策略设计模式?(举例说明)
      • 三:如何使用策略设计模式?(举例说明)
        • 3-1:定义抽象策略角色 Strategy
        • 3-2:实现具体的策略角色 Concrete Strategy
        • 3-3:创建环境角色 Context
        • 3-4:创建特定策略对象,并将其传递给环境角色 Context
      • 四:策略模式的优缺点
      • 五:策略模式的其它应用

一:什么是策略设计模式?

策略设计模式是行为型设计模式之一,它在 Gof Book 书中的描述如下:

在计算机编程中,策略模式是一种行为软件设计模式,允许在运行时选择算法。代码不是直接实现单个算法,而是接收运行时指令,决定使用哪一组算法。

标准策略模式的 UML 如下图所示

由上图可以看出

  • Strategy(策略)- 也可以叫抽象策略角色,用以声明一个支持所有算法的接口,并通过 Context 来执行特定策略的方法;
  • Concrete Strategy(具体策略)- 也可以叫具体策略角色,使用 Strategy 接口实现不同的算法。Context 只是使用这个接口,并不关心算法的具体实现;
  • Context(上下文)- 也可以叫环境角色,保存对 Strategy 对象的引用,但不依赖于算法的实现方式。

最后由客户端创建一个特定的策略对象,并将其传递给 Context 即可。

二:为什么要使用策略设计模式?(举例说明)

策略模式允许在运行时选择算法或行为,将算法的使用和实现分离,提高系统的灵活性和可扩展性。

举例说明:

在举例策略模式之前,我们先来看一下,同样的需求,如果不使用策略模式,而是使用一般的 if…else 条件语句来处理会有什么问题。

某视频剪辑类 APP 提供了不同的工具如链接转文字、视频转文字、智能配音和去水印等功能,APP 刚上线为吸引更多的用户,所以允许用户免费使用这些工具。因为目前只有一种免费的支付方式,此时后端定义的获取订单号接口只需要传入工具的 id 就可以了,所以你的代码可能是这样写的。

... tag1
/// 支付状态
Future<bool> payStatus(int toolId) async {// 创建工具订单号final String orderNo = await getToolsOrder(toolId: toolId);// 根据订单号后等待工具处理的结果final bool res = await doSomething(orderNo);return res;
}

后来用户量上来了,APP 内对使用工具做了以下调整。将原先的免费使用工具更改为每天可免费使用某工具一定的次数,免费次数用完后再使用工具需要通过观看广告后才可以。现在的支付方式增加到了两种,为此你定义了一个支付类型的枚举

/// 支付类型
enum PayOrderType {free, // 免费ad, // 看广告
}

后端提供的获取订单号接口也增加了一个支付类型的入参,只针对免费支付类型而言,此时 tag1 代码需要做如下调整

... tag2
Future<bool> payStatus(int toolId, PayOrderType payOrderType) async {if (payOrderType == PayOrderType.free) {final String orderNo = await getToolsOrder(toolId: toolId, payOrderType: PayOrderType.free);final bool res = await doSomething(orderNo);return res;}... 暂时省略部分代码return false;
}

可以对比下 tag1 和 tag2 的代码修改了哪里,不知道你发现什么问题了没,如果没有也没关系,我们继续往下看。

如果是看广告类型的支付方式,调用第三方广告 SDK 时,要求需要传入广告位的标识 key,为此 tag2 的代码不得不再增加一个入参 positionKey,并增加调用观看广告的方法以及是否观看完的判断,如下代码所示。

... tag3
Future<bool> payStatus(int toolId, PayOrderType payOrderType, String positionKey) async {if (payOrderType == PayOrderType.free) {final String orderNo = await getToolsOrder(toolId: toolId, payOrderType: PayOrderType.free);final bool res = await doSomething(orderNo);return res;}else if (payOrderType == PayOrderType.ad) {final String orderNo = await getToolsOrder(toolId: toolId, payOrderType: PayOrderType.ad);// 看广告final ADResult adRes = await openRewardAD(positionKey);if (adRes.code != 1) {return false;}final bool res = await doSomething(orderNo);return res;}return false;
}

这样写也能实现需求,但不知道你发现一个问题没,随着支付方式的增加,payStatus 方法就会不断的根据实际情况增加入参并实现相关支付方式的代码,且不断的增加新的 else if 判断,这违背了六大设计原则之一的 OCP(Open Close Principle)开放封闭原则,也就是对扩展开放,但对修改关闭

我们应该需要这么一种设计理念,当后面再增加新的需求时,应该是在不变动当前正常运行的代码下,通过其他方式新增代码实现新需求。如果为了新需求而改动原有代码,可能会造成其他调用原本代码的地方发生预期之外的错误。

此时,我们的主角策略模式,终于要闪亮登场了。

三:如何使用策略设计模式?(举例说明)

基于目录二的需求,我们通过观察发现,无论是哪种支付方式,我们都是先根据传入的工具 id 创建订单号,然后在工具处理完成后返回结果,所以可以把这个行为抽象出来一个接口,也就是使用策略模式的第一步。

3-1:定义抽象策略角色 Strategy
/// 策略公共接口
abstract class IPayStrategy {Future<bool> payStatus(int toolId);
}
3-2:实现具体的策略角色 Concrete Strategy

对于目录二的免费支付方式而言,具体的实现如下代码所示

/// 免费支付策略
final class PayStrategyByFree implements IPayStrategy {@overrideFuture<bool> payStatus(int toolId) async {final String orderNo = await getToolsOrder(toolId: toolId, payOrderType: PayOrderType.free);final bool res = await doSomething(orderNo);return res;}
}

对于目录二的看广告支付方式而言,具体的实现如下代码所示

/// 看广告支付策略
final class PayStrategyByAD implements IPayStrategy {final String positionKey; // 广告位标识 keyPayStrategyByAD(this.positionKey);@overrideFuture<bool> payStatus(int toolId) async {final String orderNo = await getToolsOrder(toolId: toolId, payOrderType: PayOrderType.ad);// 看广告final ADResult adRes = await openRewardAD(positionKey);if (adRes.code != 1) {return false;}final bool res = await doSomething(orderNo);return res;}
}

可以看出,看广告时所需的广告位标识 key,由支付策略类的构造函数传入。

3-3:创建环境角色 Context

抽象策略和具体策略都已实现,现在创建环境角色 Context,其持有对抽象策略的引用,并决定使用哪种策略。

/// 支付上下文,持有一个策略对象的引用
class PayContext {final IPayStrategy iPayStrategy;PayContext({required this.iPayStrategy});Future<bool> getPayStatus(int toolId) async {return iPayStrategy.payStatus(toolId);}
}
3-4:创建特定策略对象,并将其传递给环境角色 Context

一切的铺垫都已完成,光说不练假把式,现在就让我们把策略模式应用在支付方式上吧。

// 工具是否免费
bool isFree = false;
// 支付策略
late IPayStrategy strategy;
// 免费的支付策略
if (isFree) {strategy = PayStrategyByFree();
} 
// 看广告的支付策略
else {strategy = PayStrategyByAD();
}
// 支付状态(传入具体的支付策略)
final bool success = await PayContext(iPayStrategy: strategy).getPayStatus(123456);
// ... 根据支付状态处理后续业务

这里首先根据条件创建了不同的策略对象,然后把策略对象传递给了环境角色 Context,由环境角色 Context 负责调用具体的策略方法。后面如果再增加其他的支付方式的话,只需要再声明一个策略类并实现具体的算法即可。相比较使用 if…else 的条件语句来说,对外提供了扩展,对内又限制修改,符合 OCP 原则。

至此,关于什么是策略模式以及如何使用策略模式便介绍到这里了。

四:策略模式的优缺点

没有最好的设计模式,只用相对合适的设计模式。策略模式的优缺点如下

优点:

  • 算法可以自由切换
  • 避免使用了多重条件判断
  • 扩展性良好

缺点:

  • 策略类会逐渐增多
  • 所有策略类都需要对外暴露

五:策略模式的其它应用

除了本文举例的支付方式可以使用策略模式外,符合策略模式定义和使用场景的都可以使用该模式。如

  1. 支付选项策略。如支付类型是使用微信、支付宝、信用卡还是银行转账等支付类型。
  2. 游戏伤害计算。如使用不同的招式,技能,为不同的攻击定义不同的算法。
  3. 排序算法。如把不同的算法(冒泡排序、快排排序、选择排序等)通过不同的策略类实现,最终调用公共的 Sort 接口。
  4. 电商平台优惠卷系统。如不同的优惠卷有不同的使用策略,例如满减、打折、买赠等方式。

还有很多其它案例就不一一说明了,知道什么时候该用策略模式以及如何使用策略模式即可,以不变应万变。

你的问题得到解决了吗?欢迎在评论区留言。

赠人玫瑰,手有余香,如果觉得文章不错,希望可以给个一键三连,感谢。


结束语

技术是一点一点积累的,大神也不是一天就可以达到的。原地不动就是退步,所以每天进步一点点。

最后,附上一句格言:"好学若饥,谦卑若愚",望共勉。

相关文章:

【Dart 教程系列第 49 篇】什么是策略设计模式?如何在 Dart 中使用策略设计模式

这是【Dart 教程系列第 49 篇】&#xff0c;如果觉得有用的话&#xff0c;欢迎关注专栏。 博文当前所用 Flutter SDK&#xff1a;3.22.1、Dart SDK&#xff1a;3.4.1 文章目录 一&#xff1a;什么是策略设计模式&#xff1f;二&#xff1a;为什么要使用策略设计模式&#xff1…...

BGP路由反射器

原理概述 缺省情况下&#xff0c;路由器从它的一个 IBGP对等体那里接收到的路由条目不会被该路由器再传递给其他IBGP对等体&#xff0c;这个原则称为BGP水平分割原则&#xff0c;该原则的根本作用是防止 AS内部的BGP路由环路。因此&#xff0c;在AS内部&#xff0c;一般需要每台…...

DolphinDB Web 端权限管理:可视化操作指南

在现代数据库管理中&#xff0c;高效和直观的权限管理对于用户的数据安全是至关重要的。过去 DolphinDB 用户需要依赖系统脚本来管理用户和权限&#xff0c;这对于缺乏技术背景的管理员来说既复杂又容易出错。 为了提升用户体验和操作效率&#xff0c;DolphinDB 目前在 Web 上…...

学习Vue2收藏这一篇就够了(如何创建Vue实例)

什么是Vue&#xff1f; Vue是什么&#xff1a;是一个用于构建用户界面的渐进式框架 什么是构建用户界面&#xff1a;基于数据动态渲染页面 什么是渐进式&#xff1a;循序渐进的学习 什么是框架&#xff1a;一整套完整的项目解决方案 创建Vue实例 核心步骤&#xff08;4步…...

Mysql数据库第四次作业

mysql> create table student(sno int primary key auto_increment,sname varchar(30) not null unique,Ssex varchar(2) check (Ssex男 or Ssex女) not null,Sage int not null,Sdept varchar(10) default计算机 not null); mysql> create table Course(Con int primar…...

使用Docker搭建MySql的主从同步+ShardingSphere搭建Mysql的读写分离

参考课程 尚硅谷ShardingSphere5实战教程&#xff08;快速入门掌握核心&#xff09;_哔哩哔哩_bilibili 主服务器 创建容器 docker run -d \ -p 3306:3306 \ -v /kira/mysql/master/conf:/etc/mysql/conf.d \ -v /kira/mysql/master/data:/var/lib/mysql \ -e MYSQL_ROOT…...

数据结构:数据类型与抽象数据类型

数据类型与抽象数据类型 数据类型基本数据类型构造数据类型指针类型枚举类型 抽象数据类型&#xff08;ADT&#xff09;抽象数据类型的组成部分常见的抽象数据类型示例 数据类型与抽象数据类型的区别实现抽象数据类型的具体方式用数组实现栈用链表实现栈 总结 数据类型 数据类…...

西方逻辑史简介

西方逻辑史研究&#xff0c;对形式逻辑实现现代化&#xff0c;对加强西方哲学史研究&#xff0c;对开展科学方法论的研究都有重要意义。西方逻辑史一般被划分成古代、中世纪、现代三个历史时期。本文拟对这三个时期中的七个重要逻辑学家和逻辑学派&#xff1a;亚里士多德、斯多…...

【论文10】复现代码tips

一、准备工作 1.创建一个虚拟环境 conda create --name drgcnn38 python=3.8.18 2.激活虚拟环境 conda activate drgcnn38 注意事项 在Pycharm中终端(terminal)显示PS而不是虚拟环境base 问题如下所示 解决方法:shell路径改成cmd.exe 重启终端显示虚拟环境 3.安装torch …...

分布式缓存获取以及设置

1. 通用代码 public SysUser getCache(String sysUserId) {String cacheKey "litgery:warehouse:" sysUserId;// 尝试从缓存中获取数据CacheData cacheData redisUtils.get(cacheKey);if (null ! cacheData) {if (Boolean.TRUE.equals(cacheData.getExist())) {re…...

SMO算法,platt论文的原始算法及优化算法

platt论文&#xff1a;[PDF] Sequential Minimal Optimization : A Fast Algorithm for Training Support Vector Machines | Semantic Scholar 算法优化&#xff1a;[PDF] Improvements to Platts SMO Algorithm for SVM Classifier Design | Semantic Scholar 包含个人plat…...

2.3 openCv -- 对矩阵执行掩码操作

在矩阵上进行掩模操作相当简单。其基本思想是根据一个掩模矩阵(也称为核)来重新计算图像中每个像素的值。这个掩模矩阵包含的值决定了邻近像素(以及当前像素本身)对新的像素值产生多少影响。从数学角度来看,我们使用指定的值来做一个加权平均。 具体而言,掩模操作通常涉…...

【Django】 js实现动态赋值、显示show隐藏hide效果

文章目录 需要达到的前端效果预览&#xff1a;实现步骤复制bootstrp代码&#xff08;buttons&#xff09;复制bootstrp代码&#xff08;Alert警告框&#xff09;写js测试效果 需要达到的前端效果预览&#xff1a; {% load static %} <!DOCTYPE html> <html lang"…...

qt--做一个拷贝文件器

一、项目要求 使用线程完善文件拷贝器的操作 主窗口不能假死主窗口进度条必须能动改写文件大小的单位&#xff08;自适应&#xff09; 1TB1024GB 1GB1024MB 1MB1024KB 1KB1024字节 二、所需技术 1.QFileDialog 文件对话框 QFileDialog也继承了QDialog类&#xff0c;直接使用静态…...

Eclipse 搭建 C/C++ 开发环境以及eclipse的使用

一、下载、安装 MinGW 1、下载: 下载地址&#xff1a;MinGW - Minimalist GNU for Windows - Browse Files at SourceForge.net 点击“Download Latest Version”即可 下载完成后&#xff0c;得到一个名为 mingw-get-setup.exe 的安装文件。双击运行&#xff0c;安装即可。 …...

【初阶数据结构】复杂度算法题篇

旋转数组 力扣原题 方案一 循环K次将数组所有元素向后移动⼀位&#xff08;代码不通过) 时间复杂度O(n2) 空间复杂度O(1) void rotate(int* nums, int numsSize, int k) {while (k--) {int end nums[numsSize - 1];for (int i numsSize - 1; i > 0; i--) {nums[i] num…...

20240725项目的maven环境报红-重新配置maven

1.在编辑器里面打开项目&#xff0c;导入源码 &#xff08;1&#xff09;找到项目的地址C:\Users\zzz\IdeaProjects\datasys&#xff0c;然后右击用idea编辑器打开。 &#xff08;2&#xff09;idea中上菜单栏打开open&#xff0c;然后输入file&#xff0c;选择源代码文件 2.…...

若依 ruoyi poi Excel合并行的导入

本文仅针对文字相关的合并做了处理 &#xff0c;图片合并及保存需要另做处理&#xff01;&#xff01; 目标&#xff1a;Excel合并行内容的导入 结果&#xff1a; 1. ExcelUtil.java 类&#xff0c;新增方法&#xff1a;判断是否是合并行 /*** 新增 合并行相关代码&#xff1a;…...

优化算法:1.遗传算法(GA)及Python实现

一、定义 遗传算法就像是在模拟“优胜劣汰”的进化过程&#xff0c;通过选择最优秀的个体&#xff0c;交配产生下一代&#xff0c;并引入一定的变异&#xff0c;逐步优化解决问题。 二、具体步骤 初始化种群(Initialization)&#xff1a; 假设你要找到一个迷宫的最佳出口路径。…...

企业化运维(8)Docker容器技术

###1.Docker介绍### 什么是Docker Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中&#xff0c;然后发布到任何流行的 Linux或Windows 机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;相互之间…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战

前言 现在我们有个如下的需求&#xff0c;设计一个邮件发奖的小系统&#xff0c; 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其…...

【git】把本地更改提交远程新分支feature_g

创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

深度学习习题2

1.如果增加神经网络的宽度&#xff0c;精确度会增加到一个特定阈值后&#xff0c;便开始降低。造成这一现象的可能原因是什么&#xff1f; A、即使增加卷积核的数量&#xff0c;只有少部分的核会被用作预测 B、当卷积核数量增加时&#xff0c;神经网络的预测能力会降低 C、当卷…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

《Docker》架构

文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器&#xff0c;docker&#xff0c;镜像&#xff0c;k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...

QT开发技术【ffmpeg + QAudioOutput】音乐播放器

一、 介绍 使用ffmpeg 4.2.2 在数字化浪潮席卷全球的当下&#xff0c;音视频内容犹如璀璨繁星&#xff0c;点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频&#xff0c;到在线课堂中知识渊博的专家授课&#xff0c;再到影视平台上扣人心弦的高清大片&#xff0c;音…...

云原生安全实战:API网关Envoy的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关 作为微服务架构的统一入口&#xff0c;负责路由转发、安全控制、流量管理等核心功能。 2. Envoy 由Lyft开源的高性能云原生…...