如何抽象策略模式

策略模式是什么


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

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

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

相关文章

优先算法 —— 滑动窗口系列 - 无重复字符的最长子串

目录 前言 1. 无重复字符的最长子串 2. 题目解析 3. 算法原理 解法1&#xff1a;暴力枚举 哈希表&#xff08;判断字符是否有重复出现&#xff09; 解法2&#xff1a;滑动窗口 4. 代码 前言 当我们发现暴力解法两个指针都不回退&#xff0c;都是向同一个方向移动的时候我…

BurpSuite工具-Proxy代理用法(抓包、改包、放包)

一、Burp Suite 项目管理 二、Proxy&#xff08;代理抓包模块&#xff09; 1. 简要说明 1.1. Intercept&#xff08;拦截&#xff09; 1.2. HTTP History&#xff08;HTTP 历史&#xff09; 1.3. WebSockets History&#xff08;WebSocket 历史&#xff09; 1.4. Options…

Marvell第四季度营收预计超预期,定制芯片需求激增

芯片制造商Marvell Technology&#xff08;美满电子科技&#xff09;&#xff08;MRVL&#xff09;在周二发布了强劲的业绩预告&#xff0c;预计第四季度的营收将超过市场预期&#xff0c;得益于企业对其定制人工智能芯片的需求激增。随着人工智能技术的快速发展&#xff0c;特…

915DEBUG-obsidianTemplater使用

Templater使用 tp函数不正常显示相应数据 模板使用方式不正确 <% tp.date.now("YYYY-MM-DD") %> 应该被放置在一个被Templater识别为模板的文件中&#xff0c;或者在你使用Templater的插入模板功能时输入。如果只是在一个普通的Markdown文件中直接输入这段代码…

html+css网页设计马林旅行社移动端4个页面

htmlcss网页设计马林旅行社移动端4个页面 网页作品代码简单&#xff0c;可使用任意HTML辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 1&#…

笔记本电脑usb接口没反应怎么办?原因及解决方法

笔记本电脑的USB接口是我们日常使用中非常频繁的一个功能&#xff0c;无论是数据传输、充电还是外接设备&#xff0c;都离不开它。然而&#xff0c;当USB接口突然没有反应时&#xff0c;这无疑会给我们的工作和学习带来不小的困扰。下面&#xff0c;我们就来探讨一下笔记本USB接…

JAVA |日常开发中Servlet详解

JAVA &#xff5c;日常开发中Servlet详解 前言一、Servlet 概述1.1 定义1.2 历史背景 二、Servlet 的生命周期2.1 加载和实例化2.2 初始化&#xff08;init 方法&#xff09;2.3 服务&#xff08;service 方法&#xff09;2.4 销毁&#xff08;destroy 方法&#xff09; 三、Se…

Python酷库之旅-第三方库Pandas(255)

目录 一、用法精讲 1206、pandas.tseries.offsets.SemiMonthEnd.is_on_offset方法 1206-1、语法 1206-2、参数 1206-3、功能 1206-4、返回值 1206-5、说明 1206-6、用法 1206-6-1、数据准备 1206-6-2、代码示例 1206-6-3、结果输出 1207、pandas.tseries.offsets.S…

MySQL——操作

一.库的操作 1.基本操作 创建数据库 create database 数据库名称; 查看数据库 show databases; 删除数据库 drop database 数据库名称; 执行删除之后的结果: 数据库内部看不到对应的数据库 对应的数据库文件夹被删除&#xff0c;级联删除&#xff0c;里面的数据表全部被删…

R语言机器学习论文(三):特征提取

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载导入数据一、数据归一化二、离散型分类变量的编码三、筛选特征四、重要特征五、输出结果六、总结系统信息介绍 在数据分析和机器学习项目中,经常需要对数据进行预…

CSS 动画效果实现:图片展示与交互

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Css篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Css篇专栏内容:CSS 动画效果实现&#xff1a;图片展示与交互 前言 在现代网页设计中&#xff0c;动态效果能够显著…

928DEBUG

在vscode上的ubuntu上的wsl&#xff0c;运行一个要求clang编译器的cmake项目 安装必要的工具&#xff1a; bash sudo apt install build-essential cmake ninja-build git 安装Clang编译器&#xff1a; bash复制 sudo apt install clang DEBUG 这是因为用的是windows上的cla…

认识自定义协议

经过前面的介绍&#xff0c;我们知道TCP/IP协议有一组五层模型&#xff0c;从上往下为应用层、传输层、网络层、数据链路层和物理层&#xff0c;且在网络中传输的数据都必须经过这几层模型的封装和分用&#xff0c;作为程序员&#xff0c;我们最经常打交道的就是应用层。程序员…

nodejs循环导出多个word表格文档

文章目录 nodejs循环导出多个word表格文档一、文档模板编辑二、安装依赖三、创建导出工具类exportWord.js四、调用五、效果图nodejs循环导出多个word表格文档 结果案例: 一、文档模板编辑 二、安装依赖 // 实现word下载的主要依赖 npm install docxtemplater pizzip --save/…

多级IIR滤波效果(BIQUAD),system verilog验证

MATLAB生成IIR系数 采用率1k&#xff0c;截止频率30hz&#xff0c;Matlab生成6阶对应的biquad3级系数 Verilog测试代码 // fs1khz,fc30hz initial beginreal Sig_Orig, Noise_white, Mix_sig;real fs 1000;Int T 1; //周期int N T*fs; //1s的采样点数// 数组声明…

江南大学《2024年807自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《江南大学807自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2024年真题 Part1&#xff1a;2024年完整版真题 2024年真题

Qt 安装Qt Serial Port

最近要用Qt写个串口上位机软件&#xff0c;发现Qt的串口库用不了&#xff0c;上网找了一下资料&#xff0c;找到一种解决办法&#xff0c;具体操作如下&#xff1a; 参考文章&#xff1a;https 目录 一、找到QT安装路径&#xff0c;并运行Qt Maintenance Tool二、选择 添加或移…

matlab finv()函数解释 F分布 和 逆累积分布函数 卡方分布

1.Earths flattening 翻译并解释含义 "Earths flattening" 翻译为中文是“地球的扁率”。 含义解释&#xff1a; 地球的扁率是指地球形状偏离完美球形的程度。地球并非一个完美的球体&#xff0c;而是一个扁球体&#xff0c;即在两极略微扁平&#xff0c;赤道略微…

鸿蒙 Next 可兼容运行 Android App,还支持出海 GMS?

最近 「出境易」和 「卓易通」 应该算是鸿蒙和 Android 开发圈“突如其来”的热门话题&#xff0c;而 「出境易」可能更高频一些&#xff0c;主要也是 Next 5.0 被大家发现刚上架了一个名为「出境易」的应用&#xff0c;通过这个 App 用户可以直接运行不兼容 Next 的 Android A…

Windows实现Jenkins的自动化部署

什么是Jenkins&#xff1f;Jenkins有什么用&#xff1f; 按照我个人的理解&#xff0c;Jenkins就是一个很简单实现自动化工具&#xff0c;按照了Jenkins你就可以拥有一个类似小爱同学一样的管家&#xff0c;你可以通过你的手机步骤一个任务给你的小爱同学&#xff0c;例如打开…