当前位置: 首页 > 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;相互之间…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》

引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

高危文件识别的常用算法:原理、应用与企业场景

高危文件识别的常用算法&#xff1a;原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件&#xff0c;如包含恶意代码、敏感数据或欺诈内容的文档&#xff0c;在企业协同办公环境中&#xff08;如Teams、Google Workspace&#xff09;尤为重要。结合大模型技术&…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

日常一水C

多态 言简意赅&#xff1a;就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过&#xff0c;当子类和父类的函数名相同时&#xff0c;会隐藏父类的同名函数转而调用子类的同名函数&#xff0c;如果要调用父类的同名函数&#xff0c;那么就需要对父类进行引用&#…...