如何抽象策略模式

策略模式是什么


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

    @Override
    public 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);
        });
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【MyBatis源码】transaction包JdbcTransaction和 ManagedTransaction源码分析

&#x1f3ae; 作者主页&#xff1a;点击 &#x1f381; 完整专栏和代码&#xff1a;点击 &#x1f3e1; 博客主页&#xff1a;点击 文章目录 事务概述事务接口及工厂TransactionFactory接口Transaction接口 JDBC事务JdbcTransactiongetConnection() JdbcTransactionFactory使用…

OverLeaf

\verb|acmart| \verb&#xff1a;这是一个 LaTeX 命令&#xff0c;用来创建 “verbatim”&#xff08;字面量&#xff09;文本。它会按照你输入的内容原样输出&#xff0c;不会解析其中的任何 LaTeX 命令或者特殊字符。|&#xff1a;这是定界符。\verb 命令需要一对定界符来标…

预训练模型与ChatGPT:自然语言处理的革新与前景

目录 一、ChatGPT整体背景认知 &#xff08;一&#xff09;ChatGPT引起关注的原因 &#xff08;二&#xff09;与其他公司的竞争情况 二、NLP学习范式的发展 &#xff08;一&#xff09;规则和机器学习时期 &#xff08;二&#xff09;基于神经网络的监督学习时期 &…

楼盘智能化的关键技术:数字孪生如何落地?

随着智慧城市的不断发展&#xff0c;数字孪生技术逐渐成为实现智慧楼盘管理和运营的核心技术之一。通过创建与现实楼盘一一对应的虚拟模型&#xff0c;数字孪生不仅能够提供更加全面、动态的楼盘信息展示&#xff0c;还能为楼盘的建设、管理和用户体验优化提供精准的数据支持和…

SQLServer 服务器只接受 TLS1.0,但是客户端给的是 TLS1.2

Caused by: javax.net.ssl.SSLHandshakeException: the server selected protocol version TLS10 is not accepted by client preferences [TLS12] 原因描述&#xff1a;SQLServer 服务器只接受 TLS1.0&#xff0c;但是客户端给的是 TLS1.2 解决方法如下&#xff1a; 打开文件…

C#与PLC通讯时,数据读取和写入浮点数,字节转换问题(ModbusTCP)

在与PLC进行通讯时&#xff0c;会发现一个问题&#xff0c;浮点数1.2接收过来后&#xff0c;居然变成了两个16位的整数。 经过一系列的分析&#xff0c;这是因为在PLC存储浮点数时32位&#xff0c;我们接收过来的数据会变成两个16位的高低字节&#xff0c;而且我们进行下发数据…

AD学习笔记·空白工程的创建

编写不易&#xff0c;禁止搬运&#xff0c;仅供学习&#xff0c;感谢理解 序言 本文参考B站&#xff0c;凡亿教育&#xff0c;连接放在最后。 创建工程文件 在使用AD这个软件的电路板设计中&#xff0c;有很多的地方跟嘉立创eda还是有不一样的地方&#xff0c;其中一个地方就…

折叠屏手机拐点:三星领跌,华为小米逆势增长

科技新知 原创作者丨依蔓 编辑丨蕨影 折叠屏手机不香了&#xff1f;显示器出货量罕见下滑&#xff0c;并预计 2025 年仍将持续下降。 近日&#xff0c;市场调查机构 DSCC报告称&#xff0c; 2019 年至 2023 年&#xff0c;折叠屏市场曾保持每年至少 40% 的高速增长。然而&…

基于Java Springboot哈尔滨中心医院微信小程序

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 微信…

【人工智能-科普】图神经网络(GNN):与传统神经网络的区别与优势

文章目录 图神经网络(GNN):与传统神经网络的区别与优势什么是图神经网络?图的基本概念GNN的工作原理GNN与传统神经网络的不同1. 数据结构的不同2. 信息传递方式的不同3. 模型的可扩展性4. 局部与全局信息的结合GNN的应用领域总结图神经网络(GNN):与传统神经网络的区别与…

基于 LLamafactory 的异步API高效调用实现与速度对比

文章目录 背景摘要简介代码实现运行结果速度对比异步调用速度同步调用速度 背景 原先经常调用各家的闭源大模型的API&#xff0c;如果使用同步的方式调用&#xff0c;速度会很慢。为了加快 API 的调用速度&#xff0c;决定使用异步调用 API 的方式。 摘要 通过异步方式调用大…

ceph的存储池管理

1 查看存储池信息 查看存储池的名称 [rootceph141ceph]# ceph osd pool ls .mgr查看存储池机器编号 [rootceph141ceph]# ceph osd pool ls 1 .mgr查看存储池的详细信息 [rootceph141ceph]# ceph osd pool ls detail pool 1 .mgr replicated size 3 min_size 2 crush_rule 0 ob…

一些常见网络安全术语

1、黑帽 为非法目的进行黑客攻击的人&#xff0c;通常是为了经济利益。他们进入安全网络以销毁&#xff0c;赎回&#xff0c;修改或窃取数据&#xff0c;或使网络无法用于授权用户。这个名字来源于这样一个事实&#xff1a;老式的黑白西部电影中的恶棍很容易被电影观众识别&…

Vue中使用ECharts图表中的阈值标记(附源码)

在数据处理和可视化领域&#xff0c;我们经常需要对一系列数据点进行分析。本文将介绍如何在给定的数据点中找到对应于特定Y值的X值&#xff0c;并设置标线起始点标记在ECharts图表中&#xff0c;效果图如下&#xff1a; 实现步骤 1、数据准备 let seriesData [// 提供日期…

Windows 11 如何配置node.js

一&#xff0c;官网下载 官网首页 下载最新LTS版本&#xff0c;比较稳定&#xff0c;如果想探索更新的版本去探索新的nodejs功能。 1. 下载完成后&#xff0c;双击运行程序&#xff0c;点击next 2. 勾选接受协议&#xff0c;点击next 3. 选择自己的安装路径&#xff08;默认是…

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

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

计算机毕业设计hadoop+spark民宿推荐系统 民宿数据分析可视化大屏 民宿爬虫 民宿大数据 知识图谱 机器学习 大数据毕业设计

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

视频监控集中管理方案设计:Liveweb视频汇聚方案技术特点与应用

随着科技的发展&#xff0c;视频监控平台在各个领域的应用越来越广泛。然而&#xff0c;当前的视频监控平台仍存在一些问题&#xff0c;如视频质量不高、监控范围有限、智能化程度不够等。这些问题不仅影响了监控效果&#xff0c;也制约了视频监控平台的发展。 为了解决这些问…

SpringBoot中@Import和@ImportResource和@PropertySource

1. Import Import注解是引入java类&#xff1a; 导入Configuration注解的配置类&#xff08;4.2版本之前只可以导入配置类&#xff0c;4.2版本之后也可以导入普通类&#xff09;导入ImportSelector的实现类导入ImportBeanDefinitionRegistrar的实现类 SpringBootApplication…

css栅格系统与多列

栅格系统 栅格系统是媒体查询的具体实现 栅格系统将页面自动分为12个格子&#xff0c;可以根据不同的类前缀实现不同的页面布局 多列