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

如何抽象策略模式

策略模式是什么


策略设计模式(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);});}
}

相关文章:

如何抽象策略模式

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

BERT模型的输出格式探究以及提取出BERT 模型的CLS表示,last_hidden_state[:, 0, :]用于提取每个句子的CLS向量表示

说在前面 最近使用自己的数据集对bert-base-uncased进行了二次预训练&#xff0c;只使用了MLM任务&#xff0c;发现在加载训练好的模型进行输出CLS表示用于下游任务时&#xff0c;同一个句子的输出CLS表示都不一样&#xff0c;并且控制台输出以下警告信息。说是没有这些权重。…...

node.js实现分页,jwt鉴权机制,token,cookie和session的区别

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

34 基于单片机的指纹打卡系统

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

【Linux】用户操作命令

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

Y20030018基于Java+Springboot+mysql+jsp+layui的家政服务系统的设计与实现 源代码 文档

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

windows部署PaddleSpeech详细教程

windows安装paddlespeech步骤&#xff1a; 1. 安装vs c编译环境 对于 Windows 系统&#xff0c;需要安装 Visual Studio 来完成 C 编译环境的安装。 Microsoft C Build Tools - Visual Studio 2. 安装conda conda create -y -p paddlespeech python3.8 conda activate pad…...

线程条件变量 生产者消费者模型 Linux环境 C语言实现

只能用来解决同步问题&#xff0c;且不能独立使用&#xff0c;必须配合互斥锁一起用 头文件&#xff1a;#include <pthread.h> 类型&#xff1a;pthread_cond_t PTHREAD_COND_INITIALIZER 初始化 初始化&#xff1a;int pthread_cond_init(pthread_cond_t * cond, NULL);…...

C++ packaged_task

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

【联表查询】.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的深入对比与应用

目录 一、宏定义&#xff08;Macro Definition&#xff09; 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虚拟机&#xff08;JVM&#xff09;中&#xff0c;Stop The World、OopMap 和 安全点 是与垃圾回收&#xff08;GC&#xff09;和性能优化密切相关的概念。理…...

【openGauss︱PostgreSQL】openGauss或PostgreSQL查表、索引、序列、权限、函数

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

Dataset用load_dataset读图片和对应的caption的一个坑

代码&#xff1a; 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,) 数据&#xff1…...

【信息系统项目管理师】第7章:项目立项管理 考点梳理

文章目录 7.1 项目建议与立项申请7.2 项目可行性研究7.2.1 可行性研究的内容7.2.2 初步可行性研究7.2.3 详细可行性研究&#xff08;重点&#xff09; 7.3 项目评估与决策 【学习建议】本章大概考选择题2分左右&#xff0c;有可能考案例题。论文早年考过。本章知识点比较集中&a…...

知识库、提示词对大语言模型的影响测试

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

vistat-监控和分析网络状态

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

EasyAnimateV5 视频生成大模型原理详解与模型使用

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

水稻和拟南芥生命周期中单碱基分辨率的m6A定量分析-文献精读88

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

学习threejs,使用canvas更新纹理

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️Texture 贴图 二、&#x1…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

PL0语法,分析器实现!

简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

GitHub 趋势日报 (2025年06月06日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...

数据结构:递归的种类(Types of Recursion)

目录 尾递归&#xff08;Tail Recursion&#xff09; 什么是 Loop&#xff08;循环&#xff09;&#xff1f; 复杂度分析 头递归&#xff08;Head Recursion&#xff09; 树形递归&#xff08;Tree Recursion&#xff09; 线性递归&#xff08;Linear Recursion&#xff09;…...

CppCon 2015 学习:Time Programming Fundamentals

Civil Time 公历时间 特点&#xff1a; 共 6 个字段&#xff1a; Year&#xff08;年&#xff09;Month&#xff08;月&#xff09;Day&#xff08;日&#xff09;Hour&#xff08;小时&#xff09;Minute&#xff08;分钟&#xff09;Second&#xff08;秒&#xff09; 表示…...

CppCon 2015 学习:Reactive Stream Processing in Industrial IoT using DDS and Rx

“Reactive Stream Processing in Industrial IoT using DDS and Rx” 是指在工业物联网&#xff08;IIoT&#xff09;场景中&#xff0c;结合 DDS&#xff08;Data Distribution Service&#xff09; 和 Rx&#xff08;Reactive Extensions&#xff09; 技术&#xff0c;实现 …...

react-pdf(pdfjs-dist)如何兼容老浏览器(chrome 49)

之前都是使用react-pdf来渲染pdf文件&#xff0c;这次有个需求是要兼容xp环境&#xff0c;xp上chrome最高支持到49&#xff0c;虽然说iframe或者embed都可以实现预览pdf&#xff0c;但为了后续的定制化需求&#xff0c;还是需要使用js库来渲染。 chrome 49测试环境 能用的测试…...

视觉slam--框架

视觉里程计的框架 传感器 VO--front end VO的缺点 后端--back end 后端对什么数据进行优化 利用什么数据进行优化的 后端是怎么进行优化的 回环检测 建图 建图是指构建地图的过程。 构建的地图是点云地图还是什么信息的地图&#xff1f; 建图并没有一个固定的形式和算法…...

【Elasticsearch基础】Elasticsearch批量操作(Bulk API)深度解析与实践指南

目录 1 Bulk API概述 1.1 什么是批量操作 1.2 Bulk API的优势 2 Bulk API的工作原理 2.1 请求处理流程 2.2 底层机制 3 Bulk API的使用方法 3.1 基本请求格式 3.2 操作类型示例 3.3 响应格式 4 Bulk API的最佳实践 4.1 批量大小优化 4.2 错误处理策略 4.3 性能调…...

MAZANOKE结合内网穿透技术实现跨地域图像优化服务的远程访问过程

文章目录 前言1. 关于MAZANOKE2. Docker部署3. 简单使用MAZANOKE4. 安装cpolar内网穿透5. 配置公网地址6. 配置固定公网地址总结 前言 在数字世界高速发展的今天&#xff0c;您是否察觉到那些静默增长的视觉数据正在悄然蚕食存储空间&#xff1f;随着影像记录成为日常习惯&…...