【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. 电商平台优惠卷系统。如不同的优惠卷有不同的使用策略,例如满减、打折、买赠等方式。

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

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

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


结束语

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue element ui分页组件示例

https://andi.cn/page/621615.html

Ubuntu安装mysql,并使用IDEA连接mysql

一、安装Mysql 1.更新源 sudo apt-get update2.安装Mysql apt-get install mysql-server3.检查是否安装成功 mysql --version4.启动和关闭mysql的命令如下: #启动 sudo service mysql start #关闭 sudo service mysql stop #重启 sudo service mysql restart5.查看mysql运行…

19145 最长无重复子数组

这个问题可以使用滑动窗口的方法来解决。我们可以使用两个指针&#xff0c;一个指向子数组的开始&#xff0c;一个指向子数组的结束。然后我们使用一个哈希表来记录每个元素最后出现的位置。当我们遇到一个已经在子数组中出现过的元素时&#xff0c;我们就将开始指针移动到这个…

【数据结构】顺序表(c语言实现)(附源码)

​ &#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;数据结构 目录 前言 1.顺序表的概念与结构 2.顺序表的分类 3.顺序表的实现 3.1 结构定义及方法的声明 3.2 方法的实现 3.2.1 初始化 3.2.2 销毁 3.2…

科技与占星的融合:AI 智能占星师

本文由 ChatMoney团队出品 在科技的前沿领域&#xff0c;诞生了一位独特的存在——AI占星师。它并非传统意义上的占星师&#xff0c;而是融合了先进的人工智能技术与神秘的占星学知识。 这能够凭借其强大的数据分析能力和精准的算法&#xff0c;对星辰的排列和宇宙的能量进行深…

基于SpringBoot实现验证码功能

目录 一 实现思路 二 代码实现 三 代码汇总 现在的登录都需要输入验证码用来检测是否是真人登录&#xff0c;所以验证码功能在现在是非常普遍的&#xff0c;那么接下来我们就基于springboot来实现验证码功能。 一 实现思路 今天我们介绍的是两种主流的验证码&#xff0c;一…

Bouncy Castle集成SM2与SM3

在Bouncy Castle库中&#xff0c;SM2和SM3是两种分别用于非对称加密和数字签名的密码算法&#xff0c;它们也可以结合使用&#xff0c;形成一种高安全性的加密签名方案&#xff0c;即SM2withSM3。以下是对SM2SM3的详细解释&#xff1a; 一、SM2算法 SM2是一种由中国国家密码管…

GEE:设置ui.Map.Layer上交互矢量边界填充颜色为空,只显示边界

一、目标 最近在GEE的交互功能鼓捣一些事情&#xff0c;在利用buffer功能实现了通过选点建立一个矩形后&#xff0c;需要将该矩形填充颜色设为空&#xff0c;只留边界。 然而通过正常设置layer的可视化参数并不能实现这一目的。因此只能另辟蹊径&#xff0c;改为定义矢量边界…

VMware 上安装 CentOS 7 教程 (包含网络设置)

**建议先看一些我安装VMware的教程&#xff0c;有些网络配置需要做一下 1.打开VMware&#xff0c;创建虚拟机 2.勾选自定义&#xff0c;点击下一步 3.点击下一步 4.勾选“稍后安装操作系统”&#xff0c;点击下一步 5.勾选linux&#xff0c;勾选centos7&#xff0c;点击下一步…

pytorch-训练自定义数据集实战

目录 1. 步骤2. 加载数据2.1 继承Dataset2.1.1 生成name2label2.1.2 生成image path, label的文件2.1.3 __len__2.1.3 __getitem__2.1.4 数据切分为train、val、test 3. 建立模型4. 训练和测试4. 完整代码 1. 步骤 加载数据创建模型训练和测试迁移学习 2. 加载数据 这里以宝…

Minos 多主机分布式 docker-compose 集群部署

参考 docker-compose搭建多主机分布式minio - 会bk的鱼 - 博客园 (cnblogs.com) 【运维】docker-compose安装minio集群-CSDN博客 Minio 是个基于 Golang 编写的开源对象存储套件&#xff0c;虽然轻量&#xff0c;却拥有着不错的性能 中文地址&#xff1a;MinIO | 用于AI的S3 …

自学JavaScript(放假在家自学第一天)

目录 JavaScript介绍分为以下几点 1.1 JavaScript 是什么 1.2JavaScript书写位置 1.3 Javascript注释 1.4 Javascript结束符 1.5 Javascript输入输出语法 JavaScript(是什么?) 是一种运行在客户端(浏览器)的编程语言&#xff0c;实现人机交互效果。 2.作用(做什么?)网…

PCL-基于超体聚类的LCCP点云分割

目录 一、LCCP方法二、代码实现三、实验结果四、总结五、相关链接 一、LCCP方法 LCCP指的是Local Convexity-Constrained Patch&#xff0c;即局部凸约束补丁的意思。LCCP方法的基本思想是在图像中找到局部区域内的凸结构&#xff0c;并将这些结构用于分割图像或提取特征。这种…

入门 PyQt6 看过来(案例)13~ 制作一个颜色调节器

本文给大家带来一个利用pyqt制作的颜色调节器&#xff0c;通过拨动滚动条或者旋钮就可以调整rgb三色进行颜色的微调&#xff0c;效果如下&#xff1a; 本文实现的是不同的UI设计&#xff0c;实现的相同的功能&#xff0c;我们先分析以下思路&#xff1a; 首先进行UI页面设计分析…

SSL/TLS和SSL VPN

1、SSL/TLS SSL安全套接字层&#xff1a;是一种加密协议&#xff0c;用于在网络通信中建立安全连接。它在应用层和传输层&#xff08;TCP/IP&#xff09;之间提供数据加密、服务器身份验证以及信息完整性验证 SSL只保护TCP流量&#xff0c;不保护UDP协议 TLS&#xff1a;传输层…

VulnHub:cengbox1

靶机下载地址&#xff0c;下载完成后&#xff0c;用VirtualBox打开靶机并修改网络为桥接即可搭建成功。 信息收集 主机发现和端口扫描 扫描攻击机&#xff08;192.168.31.218&#xff09;同网段存活主机确认目标机ip&#xff0c;并对目标机进行全面扫描。 nmap 192.168.31.…

【VS2019安装+QT配置】

【VS2019安装QT配置】 1. 前言2. 下载visual studio20193. visual studio2019安装4. 环境配置4.1 系统环境变量配置4.2 qt插件开发 5. Visual Studio导入QT项目6. 总结 1. 前言 前期安装了qt&#xff0c;发现creator编辑器并不好用&#xff0c;一点都不时髦。在李大师的指导下&…

[网鼎杯 2020 朱雀组]Nmap(详细解读版)

这道题考察nmap的一些用法,以及escapeshellarg和escapeshellcmd两个函数的绕过&#xff0c;可以看这里PHP escapeshellarg()escapeshellcmd() 之殇 (seebug.org) 两种解题方法&#xff1a; 第一种通过nmap的-iL参数读取扫描一个文件到指定文件中第二种是利用nmap的参数写入we…

昇思25天学习打卡营第1天|快速入门-构建基于MNIST数据集的手写数字识别模型

非常感谢华为昇思大模型平台和CSDN邀请体验昇思大模型&#xff01;从今天起&#xff0c;我将以打卡的方式&#xff0c;结合原文搬运和个人思考&#xff0c;分享25天的学习内容与成果。为了提升文章质量和阅读体验&#xff0c;我会将思考部分放在最后&#xff0c;供大家探索讨论…

java-数据结构与算法-02-数据结构-05-栈

文章目录 1. 栈1. 概述2. 链表实现3. 数组实现4. 应用 2. 习题E01. 有效的括号-Leetcode 20E02. 后缀表达式求值-Leetcode 120E03. 中缀表达式转后缀E04. 双栈模拟队列-Leetcode 232E05. 单队列模拟栈-Leetcode 225 1. 栈 1. 概述 计算机科学中&#xff0c;stack 是一种线性的…