如何抽象策略模式
策略模式是什么
策略设计模式(Strategy Pattern)是一种面向对象设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。这种模式使得算法可以独立于使用它们的客户端而变化。
策略设计模式包含三个主要的角色:
1环境(Context):持有一个策略对象,并调用其算法。
2策略(Strategy):定义了一组算法,并将每个算法封装起来,使它们可以相互替换。
3具体策略(ConcreteStrategy):实现了策略接口,提供了具体的算法实现。
在策略设计模式中,环境持有一个策略对象,并通过调用策略的算法来完成具体的任务。策略对象可以根据需要进行替换,从而实现不同的算法实现,并且可以在运行时动态地更改策略对象。
看到上面的介绍可能不太明白策略模式具体为何物,这里会从最基本的代码说起,一步一步彻底掌握此模式。
优惠类型实战
下述代码可能大家都能联想出对应的业务,根据对应的优惠类型,对价格作出相应的优惠。
这段代码是能够满足项目中业务需求的,而且很多已上线生产环境的代码也有这类代码。但是,这一段代码存在存在两个弊端:
1代码的复杂性,正常业务代码逻辑肯定会比这个代码块复杂很多,这也就 导致了 if-else 的分支以及代码数量过多。这种方式可以通过将代码拆分成独立函数或者拆分成类来解决。
2开闭原则,价格优惠肯定会 随着不同的时期作出不同的改变,或许新增、删除或修改。如果在一个函数中修改无疑是件恐怖的事情,想想可能多个开发者分别进行开发,杂乱无章的注释,混乱的代码逻辑等情况十有八九会发生。
如何运用策略模式优化上述代码,使程序设计看着简约、可扩展等特性。
1简化代码的复杂性,将不同的优惠类型定义为不同的策略算法实现类。
2保证开闭原则,增加程序的健壮性以及可扩展性。
将上述代码块改造为策略设计模式,大致需要三个步骤。
1定义抽象策略接口,因为业务使用接口而不是具体的实现类的话,便可以灵活的替换不同的策略;
2定义具体策略实现类,实现自抽象策略接口,其内部封装具体的业务实现;
3定义策略工厂,封装创建策略实现(算法),对客户端屏蔽具体的创建细节。
目前把抽象策略接口、具体的策略实现类以及策略工厂都已经创建了,现在可以看一下客户端需要如何调用,又是如何对客户端屏蔽具体实现细节的。
根据代码块图片得知,具体策略类是从策略工厂中获取,确实是取消了 if-else 设计,在工厂中使用 Map 存储策略实现。获取到策略类后执行具体的优惠策略方法就可以获取优惠后的金额。
通过分析大家得知,目前这种设计确实将应用代码的复杂性降低了。如果新增一个优惠策略,只需要新增一个策略算法实现类即可。但是,添加一个策略算法实现,意味着需要改动策略工厂中的代码,还是不符合开闭原则。
如何完整实现符合开闭原则的策略模式,需要借助 Spring 的帮助,详细案例请继续往下看。
策略模式结合 Spring
最近项目中设计的一个功能用到了策略模式,分为两类角色,笔者负责定义抽象策略接口以及策略工厂,不同的策略算法需要各个业务方去实现,可以联想到上文中的优惠券功能。因为是 Spring 项目,所以都是按照 Spring 的方式进行处理。
可以看到,比对上面的示例代码,有两处明显的变化:
1抽象策略接口中,新定义了 mark() 接口,此接口用来标示算法的唯一性;
2具体策略实现类,使用 @Component 修饰,将对象本身交由 Spring 进行管理 。
小贴士:为了阅读方便,mark() 返回直接使用字符串替代,读者朋友在返回标示时最好使用枚举定义。
接下来继续查看抽象策略工厂如何改造,才能满足开闭原则。
通过 InitializingBean 接口实现中调用 IOC 容器查找对应策略实现,随后将策略实现 mark() 方法返回值作为 key, 策略实现本身作为 value 添加到 Map 容器中等待客户端的调用。
这里使用的 SpringBoot 测试类,注入策略工厂 Bean,通过策略工厂选择出具体的策略算法类,继而通过算法获取到优惠后的价格。
总结下本小节,我们通过和 Spring 结合的方式,通过策略设计模式对文初的代码块进行了两块优化:应对代码的复杂性,让其满足开闭原则。
更具体一些呢就是 通过抽象策略算法类减少代码的复杂性,继而通过 Spring 的一些特性同时满足了开闭原则,现在来了新需求只要添加新的策略类即可,健壮易扩展。
策略模式优缺点
策略设计模式的优点包括:
1提高了代码的灵活性和可维护性:由于算法的实现与使用相分离,使得代码的灵活性和可维护性得到提高。当需要修改或添加新的算法时,只需要定义新的策略类,并将其传递给环境类即可,而无需修改环境类的代码。
2提高了代码的复用性:策略设计模式将算法的实现封装在策略类中,使得算法可以被多个客户端重复使用,从而提高了代码的复用性。
3可以动态地切换算法:策略设计模式将算法封装在策略类中,使得可以在运行时动态地更改算法,从而实现不同的功能和行为。这样可以使得程序更加灵活,适应不同的需求和变化。
4算法实现与使用相分离:策略设计模式将算法的实现与使用相分离,使得代码更加清晰、简洁、易于维护和扩展。由于算法的实现被封装在策略类中,客户端只需要关注如何选择和使用不同的算法即可,这样可以使得代码更加易于理解和维护。
5可以避免使用大量的条件语句:在某些情况下,需要根据不同的条件来选择不同的算法,这可能会导致代码中出现大量的条件语句,使得代码难以维护和扩展。而策略设计模式可以避免这种情况的发生,使得代码更加简洁和易于维护。
需要注意的是,在使用策略设计模式时需要注意以下几点:
1策略类之间应该是相互独立的,彼此之间不应该有任何依赖关系。这样才能确保算法的选择和替换可以在运行时动态地进行,同时也可以避免代码的耦合度过高。
2策略接口应该尽可能地简单和通用,以便于不同的策略实现类可以共用同一个接口。这样可以提高代码的复用性和灵活性,同时也可以避免接口过于复杂和难以维护。
3策略设计模式适用于需要在运行时动态切换算法的场景,如果算法的实现不需要动态切换,或者算法的实现较为简单,策略设计模式可能会显得过于复杂。因此,在选择使用策略设计模式时,需要根据具体的需求和场景进行判断和选择。
4策略设计模式可以与其他设计模式结合使用,例如工厂方法模式、单例模式等。这样可以进一步提高代码的灵活性和可维护性,同时也可以避免代码的复杂度过高。
策略模式抽象
可能细心的小伙伴会发现一个问题,当业务使用越来越多的情况下,重复定义 DiscountStrategy 以及 DiscountStrategyFactory 会增加系统冗余代码量。
可以考虑将这两个基础类抽象出来,作为基础组件库中的通用组件,供所有系统下的业务使用,从而避免代码冗余。
定义抽象策略处理接口,添加有返回值和无返回值接口。
package org.opengoofy.congomall.springboot.starter.designpattern.strategy;/*** 策略执行抽象*/
public interface AbstractExecuteStrategy<REQUEST, RESPONSE> {/*** 执行策略标识*/String mark();/*** 执行策略** @param requestParam 执行策略入参*/default void execute(REQUEST requestParam) {}/*** 执行策略,带返回值** @param requestParam 执行策略入参* @return 执行策略后返回值*/default RESPONSE executeResp(REQUEST requestParam) {return null;}
}
添加策略选择器,通过订阅 Spring 初始化事件执行扫描所有策略模式接口执行器,并根据 mark 方法定义标识添加到 abstractExecuteStrategyMap 容器中。
客户端在实际业务中使用 AbstractStrategyChoose#choose 即可完成策略模式实现。
package org.opengoofy.congomall.springboot.starter.designpattern.strategy;import org.opengoofy.congomall.springboot.starter.base.ApplicationContextHolder;
import org.opengoofy.congomall.springboot.starter.base.init.ApplicationInitializingEvent;
import org.opengoofy.congomall.springboot.starter.convention.exception.ServiceException;
import org.springframework.context.ApplicationListener;import java.util.HashMap;
import java.util.Map;
import java.util.Optional;/*** 策略选择器*/
public class AbstractStrategyChoose implements ApplicationListener<ApplicationInitializingEvent> {/*** 执行策略集合*/private final Map<String, AbstractExecuteStrategy> abstractExecuteStrategyMap = new HashMap<>();/*** 根据 mark 查询具体策略** @param mark 策略标识* @return 实际执行策略*/public AbstractExecuteStrategy choose(String mark) {return Optional.ofNullable(abstractExecuteStrategyMap.get(mark)).orElseThrow(() -> new ServiceException(String.format("[%s] 策略未定义", mark)));}/*** 根据 mark 查询具体策略并执行** @param mark 策略标识* @param requestParam 执行策略入参* @param <REQUEST> 执行策略入参范型*/public <REQUEST> void chooseAndExecute(String mark, REQUEST requestParam) {AbstractExecuteStrategy executeStrategy = choose(mark);executeStrategy.execute(requestParam);}/*** 根据 mark 查询具体策略并执行,带返回结果** @param mark 策略标识* @param requestParam 执行策略入参* @param <REQUEST> 执行策略入参范型* @param <RESPONSE> 执行策略出参范型* @return*/public <REQUEST, RESPONSE> RESPONSE chooseAndExecuteResp(String mark, REQUEST requestParam) {AbstractExecuteStrategy executeStrategy = choose(mark);return (RESPONSE) executeStrategy.executeResp(requestParam);}@Overridepublic void onApplicationEvent(ApplicationInitializingEvent event) {Map<String, AbstractExecuteStrategy> actual = ApplicationContextHolder.getBeansOfType(AbstractExecuteStrategy.class);actual.forEach((beanName, bean) -> {AbstractExecuteStrategy beanExist = abstractExecuteStrategyMap.get(bean.mark());if (beanExist != null) {throw new ServiceException(String.format("[%s] Duplicate execution policy", bean.mark()));}abstractExecuteStrategyMap.put(bean.mark(), bean);});}
}
相关文章:

如何抽象策略模式
策略模式是什么 策略设计模式(Strategy Pattern)是一种面向对象设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。这种模式使得算法可以独立于使用它们的客户端而变化。 策略设计模式包含三个主…...

BERT模型的输出格式探究以及提取出BERT 模型的CLS表示,last_hidden_state[:, 0, :]用于提取每个句子的CLS向量表示
说在前面 最近使用自己的数据集对bert-base-uncased进行了二次预训练,只使用了MLM任务,发现在加载训练好的模型进行输出CLS表示用于下游任务时,同一个句子的输出CLS表示都不一样,并且控制台输出以下警告信息。说是没有这些权重。…...

node.js实现分页,jwt鉴权机制,token,cookie和session的区别
文章目录 1. 分⻚功能2. jwt鉴权机制1.jwt是什么2.jwt的应用3.优缺点 3. cookie,token,session的对比 1. 分⻚功能 为什么要分页 如果数据量很⼤,⽐如⼏万条数据,放在⼀个⻚⾯显⽰的话显然不友好,这时候就需要采⽤分⻚…...

34 基于单片机的指纹打卡系统
目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于STC89C52RC,采用两个按键替代指纹,一个按键按下,LCD12864显示比对成功,则 采用ULN2003驱动步进电机转动,表示开门,另一个…...

【Linux】用户操作命令
声明:以下内容均学习自《Linux就该这么学》一书 1、管理员root Linux系统的管理员之所以是root,并不是因为它的名字叫root,而是因为该用户的身份号码UID(User IDentification)的数值是0。UID相当于身份证号码&#x…...

Y20030018基于Java+Springboot+mysql+jsp+layui的家政服务系统的设计与实现 源代码 文档
家政服务系统的设计与实现 1.摘要2.开发目的和意义3.系统功能设计4.系统界面截图5.源码获取 1.摘要 随着人们生活水平的提高,老龄化、少子化等多重因素影响,我国对家政服务人群的需求与日俱增。家政服务行业对我国的就业和社会效益贡献也与日俱增&#…...

windows部署PaddleSpeech详细教程
windows安装paddlespeech步骤: 1. 安装vs c编译环境 对于 Windows 系统,需要安装 Visual Studio 来完成 C 编译环境的安装。 Microsoft C Build Tools - Visual Studio 2. 安装conda conda create -y -p paddlespeech python3.8 conda activate pad…...

线程条件变量 生产者消费者模型 Linux环境 C语言实现
只能用来解决同步问题,且不能独立使用,必须配合互斥锁一起用 头文件:#include <pthread.h> 类型:pthread_cond_t PTHREAD_COND_INITIALIZER 初始化 初始化:int pthread_cond_init(pthread_cond_t * cond, NULL);…...

C++ packaged_task
packaged_task 是 C11 标准库中引入的一个模板类,它用于将可调用对象(如函数、lambda 表达式、函数对象或绑定表达式)包装起来,并允许异步地获取其结果packaged_task 类提供了一种方便的方式来创建任务,这些任务可以被…...

【联表查询】.NET开源 ORM 框架 SqlSugar 系列
.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列【数据事务…...

嵌入式C编程:宏定义与typedef的深入对比与应用
目录 一、宏定义(Macro Definition) 1.1. 特点与应用 1.1.1 定义常量 1.1.2 定义函数式宏 1.1.3 条件编译 1.2. 作用范围和生命周期方面 1.3. 应用注意事项 二、typedef 2.1. 特点与应用 2.1.1 简化类型声明 2.1.2 提高代码可读性 2.1.3 实现…...

高级java每日一道面试题-2024年12月03日-JVM篇-什么是Stop The World? 什么是OopMap? 什么是安全点?
如果有遗漏,评论区告诉我进行补充 面试官: 什么是Stop The World? 什么是OopMap? 什么是安全点? 我回答: 在Java虚拟机(JVM)中,Stop The World、OopMap 和 安全点 是与垃圾回收(GC)和性能优化密切相关的概念。理…...

【openGauss︱PostgreSQL】openGauss或PostgreSQL查表、索引、序列、权限、函数
【openGauss︱PostgreSQL】openGauss或PostgreSQL查表、索引、序列、权限、函数 一、openGauss查表二、openGauss查索引三、openGauss查序列四、openGauss查权限五、openGauss或PostgreSQL查函数六、PostgreSQL查表七、PostgreSQL查索引八、PostgreSQL查序列九、PostgreSQL查权…...

Dataset用load_dataset读图片和对应的caption的一个坑
代码: data_files {} if args.train_data_dir is not None:data_files["train"] os.path.join(args.train_data_dir, "**")dataset load_dataset("imagefolder",data_filesdata_files,cache_dirargs.cache_dir,) 数据࿱…...

【信息系统项目管理师】第7章:项目立项管理 考点梳理
文章目录 7.1 项目建议与立项申请7.2 项目可行性研究7.2.1 可行性研究的内容7.2.2 初步可行性研究7.2.3 详细可行性研究(重点) 7.3 项目评估与决策 【学习建议】本章大概考选择题2分左右,有可能考案例题。论文早年考过。本章知识点比较集中&a…...

知识库、提示词对大语言模型的影响测试
简介 通过对比有无知识库,测试大语言模型的回答 有无提示词对比测试 前提 大语言模型:Qwen1.5-1.8B-Chat-GPTQ-Int4 GPU:1650super,显存4G 提问:华为mate70 没有提示词 回答的内容如下 “华为mate70pro和mate40p…...

vistat-监控和分析网络状态
vistat 是一个用于监控和分析网络状态的工具,通常用于查看网络接口、流量、连接等实时数据。 原理:用户态调用系统接口获取内核中的网络统计信息。 核心功能:网络接口流量监控、查看连接状态、带宽使用分析。 使用方法: 查看接…...

EasyAnimateV5 视频生成大模型原理详解与模型使用
在数字内容创作中,视频扮演的角色日益重要。然而,创作高质量视频通常耗时且昂贵。EasyAnimate 系列旨在利用人工智能技术简化这一过程。EasyAnimateV5 建立在其前代版本的基础之上,不仅在质量上有所提升,还在多模态数据处理和跨语…...

水稻和拟南芥生命周期中单碱基分辨率的m6A定量分析-文献精读88
Quantitative profiling of m6A at single base resolution across the life cycle of rice and Arabidopsis 水稻和拟南芥生命周期中单碱基分辨率的m6A定量分析 水稻参考基因组(日本晴品种)-CSDN博客 “Xian”(籼)和“Geng”&…...

学习threejs,使用canvas更新纹理
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️Texture 贴图 二、…...

【笔记2-3】ESP32 bug:PSRAM chip not found or not supported 没有外部PSRAM问题解决
主要参考b站宸芯IOT老师的视频,记录自己的笔记,老师讲的主要是linux环境,但配置过程实在太多问题,就直接用windows环境了,老师也有讲一些windows的操作,只要代码会写,操作都还好,开发…...

大数据hadoop、spark、flink、kafka发展的过程
将sql 代发翻译成java 程序 将python 程序通过pyspark框架翻译成java 程序,然后运行在spark计算框架上...

设计模式-理论基础
理论基础 一、代码质量指标1.可维护性(maintainability)2.可读性(readability)3.可扩展性(extensibility)4.灵活性(flexibility)5.简洁性(simplicity)6.可复用…...

猎板 PCB特殊工艺:铸就电子行业核心竞争力新高度
在当今竞争激烈且技术驱动的电子制造领域,印制电路板(PCB)作为电子产品的关键基石,其特殊工艺的发展水平直接影响着整个行业的创新步伐与产品品质。猎板 PCB 凭借在厚铜板、孔口铺铜、HDI 板、大尺寸板以及高频高速板等特殊工艺方…...

用于目标检测的集中式特征金字塔
摘要 https://arxiv.org/pdf/2210.02093 视觉特征金字塔在多种应用中已展现出其在有效性和效率方面的优越性。然而,现有方法过度关注层间特征交互,却忽略了经验证明有益的层内特征调节。尽管一些方法试图借助注意力机制或视觉变换器来学习紧凑的层内特征表示,但它们忽略了…...

大舍传媒-关于海外媒体宣发的探讨
关于海外媒体宣发的探讨 一、海外媒体宣发的重要性 在当今全球化的时代,海外媒体宣发对于企业、组织和个人来说具有至关重要的意义。通过有效的海外媒体宣发,可以提升品牌知名度,拓展国际市场,增强影响力,吸引更多的潜…...

Python从入门到入狱
Python是从入门到入狱?这个充满调侃意味的说法在程序员圈子里流传甚广。表面看,它似乎是在嘲笑这门语言从简单易学到深陷麻烦的巨大反差,实际上却隐藏着很多值得深思的问题。要解读这个话题,得从Python的特点、使用场景以及潜在风…...

AMEYA360 | 杭晶电子:晶振在AR/VR中的应用
晶振在AR/VR设备中扮演重要角色,为其核心电子系统提供稳定的时钟信号,确保设备的高性能运行。 以下是晶振在AR/VR应用中的具体作用: 01、图像处理与同步 1、晶振为图形处理单元(GPU)和显示芯片提供精准的时钟信号,支持高速图像渲染…...

RAG评估指南:从检索到生成,全面解析LLM性能评估方法
前言 这一节我们将从时间线出发对RAG的评估方式进行对比,这些评估方式不仅限于RAG流程之中,其中基于LLM的评估方式更加适用于各行各业。 RAG常用评估方式 上一节我们讲了如何用ROUGE 这个方法评估摘要的相似度,由于篇幅限制,没…...

贪心算法实例-问题分析(C++)
贪心算法实例-问题分析 饼干分配问题 有一群孩子和一堆饼干,每个小孩都有一个饥饿度,每个饼干都有一个能量值,当饼干的能量值大于等于小孩的饥饿度时,小孩可以吃饱,求解最多有多少个孩子可以吃饱?(注:每个小孩只能吃…...