【策略模式】最佳实践——Spring IoC实现策略模式全流程深度解析

简介

策略模式是一种行为型设计模式,它定义了一系列算法,并将每一个算法封装起来,使它们可以互相替换,并且使算法的变化不会影响使用算法的客户端。策略模式通过将具体的业务逻辑从上下文(Context)中剥离出来,独立为策略类,动态地将所需的行为注入上下文对象中,从而避免代码中充斥条件判断逻辑。

策略模式的核心由以下三部分组成:

  • 策略接口(Strategy Interface):定义所有具体策略类的公共接口,用于约束具体策略的实现。
  • 具体策略类(Concrete Strategy):实现策略接口,在内部封装具体的算法和业务逻辑。
  • 上下文类(Context):持有一个具体策略对象的引用,用来调用具体策略类的方法。也可以使用工厂模式,将创建具体策略类的动作交给工厂类执行,实现业务逻辑、创建逻辑与应用程序的解耦合。

在下面的文章中,我将给策略模式的实现分为三步,从业务逻辑解耦创建逻辑解耦满足开闭原则,一步步实现策略模式的最佳实践

1.策略模式(业务逻辑解耦)

设计一个支付系统,支持多种支付方式,包括支付宝(Alipay)、微信支付(WeChatPay)和银行卡支付(BankCardPay)。不同支付方式的逻辑独立,同时系统可以根据需求动态切换支付方式。

定义策略接口

// 定义支付策略接口
public interface PaymentStrategy {void pay(double amount); // 支付方法
}

定义具体策略类

不同的具体策略类都统一继承策略接口,在类中重写策略接口定义的方法,编写具体的业务逻辑。

// 支付宝支付策略
public class AlipayStrategy implements PaymentStrategy {@Overridepublic void pay(double amount) {System.out.println("使用支付宝支付了: ¥" + amount);}
}// 微信支付策略
public class WeChatPayStrategy implements PaymentStrategy {@Overridepublic void pay(double amount) {System.out.println("使用微信支付了: ¥" + amount);}
}// 银行卡支付策略
public class BankCardStrategy implements PaymentStrategy {@Overridepublic void pay(double amount) {System.out.println("使用银行卡支付了: ¥" + amount);}
}

定义上下文类

在上下文中定义策略类的引用,使用 set 方法注入具体策略类,通过方法调用执行具体策略类的业务逻辑。

// 支付上下文类
public class PaymentContext {private PaymentStrategy strategy; // 策略引用// 动态设置支付策略public void setPaymentStrategy(PaymentStrategy strategy) {this.strategy = strategy;}// 执行支付public void pay(double amount) {if (strategy == null) {throw new IllegalStateException("未设置支付策略!");}strategy.pay(amount); // 委托给具体策略}
}

使用上下文类

在用户选用具体的支付方式时,需要给上下文 context 传入具体策略的对象,才可使用该策略的支付方法。(这种方式需要应用程序自己手动 new 出具体策略的对象,并且通过 set 方法传入给上下文类)

虽然通过这种方式,可以使得具体的支付业务逻辑与应用程序解耦合。但是依然存在两个问题:

  1. 具体策略类的创建逻辑仍然由应用程序执行,即策略类创建逻辑仍然与应用程序耦合
  2. 如果需要添加新的策略,就需要再添加一个新的 if-else 分支,因此不符合开闭原则。(开闭原则通俗点说就是在添加新的功能时,不能修改现有的代码,这里添加一个新的分支就属于修改了现有的代码)
public class Main {public static void main(String[] args) {String type = "Alipay";PaymentContext context = new PaymentContext();if (type.equals("Alipay")) {// 用户使用支付宝支付context.setPaymentStrategy(new AlipayStrategy());context.pay(15);} else if (type.equals("Wechat")) {context.setPaymentStrategy(new WeChatPayStrategy());context.pay(10);} else {context.setPaymentStrategy(new BankCardStrategy());context.pay(5);}}
}

执行结果如下:

开闭原则: 软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

对扩展开放(Open for extension):软件实体应该允许在不改变其现有代码的情况下,通过增加新功能来对其进行扩展。也就是说,当软件的需求发生变化时,我们应该能够通过添加新代码来满足这些需求,而不需要修改已有的代码。

对修改关闭(Closed for modification):一旦软件实体被开发完成并投入使用,其源代码就不应该再被修改。这可以防止对现有功能的破坏,减少引入新的错误的风险,并使软件更加稳定和可维护。

2.引入工厂模式(创建逻辑解耦)

通过引入工厂模式,可以将创建具体策略类的动作转移给工厂类执行,将策略类的创建逻辑与应用程序解耦合

定义工厂类

由工厂类负责策略类的创建逻辑,根据传入的不同参数,创建不同的具体策略类。这一步骤也就实现了策略类的创建逻辑与应用程序的解耦。

// 工厂类
public class PaymentStrategyFactory {public static PaymentStrategy getStrategy(String type) {switch (type) {case "Alipay":return new AlipayStrategy();case "WeChatPay":return new WeChatPayStrategy();case "BankCard":return new BankCardStrategy();default:throw new IllegalArgumentException("无效的支付类型");}}
}

改造工厂类

可以发现,如果需要使用大量的具体策略类的话,应用程序就需要编写大量的 if-elseswitch 分支。我们可以使用 Map 存储具体策略类,从而取消分支判断的设计。

public class PaymentStrategyFactory {// 定义Map集合private static final Map<String, PaymentStrategy> map = new HashMap<>();//static {map.put("Alipay", new AlipayStrategy());map.put("WeChatPay", new WeChatPayStrategy());map.put("BankCard", new BankCardStrategy());}public static PaymentStrategy getStrategy(String type) {// 如果存在该支付类型,返回对应的策略类if (map.containsKey(type)) return map.get(type);// 如果不存在该支付类型,抛出异常else throw new IllegalArgumentException("无效的支付类型");}
}

通过上述的改造,虽然解决了创建逻辑与应用程序耦合的问题,但是不符合开闭原则的问题仍然没有解决(如果需要添加新的具体策略类,还是需要修改工厂类的代码,在 Map 集合中 put 一个新的键值对)。

比如,如果要添加一个苹果支付的业务逻辑,就需要在 static 中添加这个代码,从而打破了开闭原则。

map.put("ApplePay", new ApplePayStrategy());

下文我们将使用 Spring IoC 解决这个问题。

使用工厂类

直接使用 PaymentStrategyFactory 工厂类的静态方法 getStrategy() 获取策略对象,调用策略对象的方法即可。

public class Main {public static void main(String[] args) {PaymentStrategy alipay = PaymentStrategyFactory.getStrategy("Alipay");alipay.pay(10);}
}

执行结果如下:

3.引入Spring IoC(满足开闭原则)

改造策略接口

需要在策略接口 PaymentStrategy 添加一个新的 mark 方法,用于标识每个接口。

public interface PaymentStrategy {void pay(double amount); // 支付方法String mark();	// 标识方法
}

改造具体策略类

具体策略类由于实现了策略接口,因此需要重写标识方法,这里直接返回对应策略的标识即可。(这一步骤很重要,应用程序通过工厂类获取策略类,就是通过这个标识获取的)

此外,我们还需要给每个具体策略类添加 @Component 注解,方便 Spring 容器管理具体策略类的生命周期。

通过 @Component 注解,实际上是通过 Spring 容器来执行 new 的步骤,也就是将具体策略类的创建逻辑由工厂类交给了 Spring 容器

@Component:可以标记任意类为 Spring Bean,Spring 容器会自动扫描和管理使用 @Component 注解标注的类。

支付宝支付策略:

@Component
public class AlipayStrategy implements PaymentStrategy {@Overridepublic void pay(double amount) {System.out.println("使用支付宝支付了: ¥" + amount);}// 返回策略的标识@Overridepublic String mark() {return "Alipay";}
}

微信支付策略:

@Component
public class WeChatPayStrategy implements PaymentStrategy {@Overridepublic void pay(double amount) {System.out.println("使用微信支付了: ¥" + amount);}@Overridepublic String mark() {return "WeChatPay";}
}

银行卡支付策略:

@Component
public class BankCardStrategy implements PaymentStrategy {@Overridepublic void pay(double amount) {System.out.println("使用银行卡支付了: ¥" + amount);}@Overridepublic String mark() {return "BankCard";}
}

改造工厂类

下面开始重头戏,我们将对工厂类进行改造,通过 Spring 容器帮我们管理策略类。

@Component
public class PaymentStrategyFactory implements InitializingBean {// 注入IoC容器@Autowiredprivate ApplicationContext context;// 定义Map集合private final Map<String, PaymentStrategy> strategyMap = new HashMap<>();// 获取对应标识的策略类public PaymentStrategy getStrategy(String type) {if (strategyMap.containsKey(type)) return strategyMap.get(type);else throw new IllegalArgumentException("无效的支付类型");}@Overridepublic void afterPropertiesSet() throws Exception {// 从IoC容器中获取所有策略接口 PaymentStrategy 的实现类,即具体策略类Map<String, PaymentStrategy> beansOfType = context.getBeansOfType(PaymentStrategy.class);beansOfType.forEach((key, value) -> strategyMap.put(value.mark(), value));}
}
  1. 添加注解:首先需要给工厂类添加 @Component 注解,后续我们将通过依赖注入的方式使用该工厂类。
@Component
  1. 实现 InitializingBean 接口:通过重写该接口的 afterPropertiesSet 方法,可以在 Bean 的初始化阶段将 Spring 容器创建好的具体策略类写入到工厂类的 strategyMap 集合中。

Spring Bean 的生命周期:整体上可以简单分为四步:实例化 —> 属性赋值 —> 初始化 —> 销毁。

public class PaymentStrategyFactory implements InitializingBean
  1. 注入 IoC 容器:使用 @Autowired 注解注入 ApplicationContext,即 IoC 容器。方便后续从容器中获取 Bean 名称和策略接口实现类的 Map 集合。
@Autowired
private ApplicationContext context;
  1. 定义 Map 集合:这个 Map 是工厂类存放策略类的集合,应用程序将从这个集合中获取对应的策略类。
// 定义Map集合
private final Map<String, PaymentStrategy> strategyMap = new HashMap<>();
  1. 编写 afterPropertiesSet 方法逻辑:这里我们需要将 Bean 名称和策略类的键值对转化为标识和策略类的键值对,方便我们根据传入的标识直接获取策略类。
@Override
public void afterPropertiesSet() throws Exception {// 4.1.从IoC容器中获取所有策略接口 PaymentStrategy 的实现类,即具体策略类Map<String, PaymentStrategy> beansOfType = context.getBeansOfType(PaymentStrategy.class);// 4.2.利用lambda表达式,将Spring容器中的策略类转移到工厂类的Map集合中beansOfType.forEach((key, value) -> strategyMap.put(value.mark(), value));
}

第一步先从注入的 Spring 容器中获取容器创建好策略类的 Map 集合,该集合存储的是 Bean 名称和策略类的键值对,如下图:

第二步使用了 lambda 表达式(如果不熟练的话使用普通 for 循环也行),将Spring容器中的策略类转移到工厂类的Map集合中,其实这一步也就是将 key 值简化,方便根据标识字符串获取策略类。如下图:

  1. 编写 getStrategy 方法逻辑:最后一步,可以从 strategyMap 集合中获取对应的策略类了。
// 获取对应标识的策略类
public PaymentStrategy getStrategy(String type) {if (strategyMap.containsKey(type)) return strategyMap.get(type);else throw new IllegalArgumentException("无效的支付类型");
}

工厂类到这里就彻底改造完毕了,我们通过将每个具体策略类都添加了 @Component 注解,Spring 启动时就会将这些使用了 @Component 注解的类创建出来并添加到 Spring IoC 容器中。这样,在添加新的策略类时就无需修改原有的工厂类,满足了开闭原则。

使用工厂类

@SpringBootTest
public class StrateTest {// 注入工厂类@Autowiredprivate PaymentStrategyFactory factory;@Testpublic void testStrategy() {PaymentStrategy alipay = factory.getStrategy("Alipay");alipay.pay(1000);}
}

执行结果如下:

总结

最佳实践小结

通过上文的三个步骤,我们一步步将策略模式改造为了最佳实践,实现了业务逻辑、创建逻辑与应用程序的解耦以及满足了开闭原则。在Spring IoC 的策略模式实现中,主要做了这三件事情:

  1. 业务逻辑与应用程序解耦:通过定义策略接口,规范不同具体策略类的统一实现,将算法的业务逻辑交给了策略类实现。
  2. 创建逻辑与应用程序解耦:通过给策略类添加 @Component 注解,由 Spring 扫描并注册具体策略类的对象到 Spring 容器中,从而将策略类的创建逻辑转移到了 Spring 容器。
  3. 满足开闭原则: 由于添加了 @Component 注解的策略类在 Spring 启动后都被注册到了 Spring 容器中,无需开发者手动硬编码到工厂类。在需要添加新的策略类时,Spring 容器可以帮助开发者自动注入。

对比其他模式

策略模式

  • 定义:定义一系列算法,将每种算法封装在独立的策略类中,使得这些算法可以互相替换,且算法的变化不会影响使用算法的上下文对象。
  • 核心思想动态选择算法,运行时可以替换策略对象。

模板方法模式

  • 定义:定义一个算法的框架,将算法的某些步骤延迟到子类中实现,使得子类可以在不改变算法结构的情况下重新定义算法的某些步骤。
  • 核心思想固定流程,子类负责填充或改写部分步骤。

工厂模式

  • 定义:定义一个用于创建对象的接口,让子类决定实例化哪个具体类。工厂模式将对象的创建过程与具体类的逻辑解耦。
  • 核心思想对象的创建,由工厂负责生产特定对象。
    者手动硬编码到工厂类。在需要添加新的策略类时,Spring 容器可以帮助开发者自动注入。

对比其他模式

策略模式

  • 定义:定义一系列算法,将每种算法封装在独立的策略类中,使得这些算法可以互相替换,且算法的变化不会影响使用算法的上下文对象。
  • 核心思想动态选择算法,运行时可以替换策略对象。

模板方法模式

  • 定义:定义一个算法的框架,将算法的某些步骤延迟到子类中实现,使得子类可以在不改变算法结构的情况下重新定义算法的某些步骤。
  • 核心思想固定流程,子类负责填充或改写部分步骤。

工厂模式

  • 定义:定义一个用于创建对象的接口,让子类决定实例化哪个具体类。工厂模式将对象的创建过程与具体类的逻辑解耦。
  • 核心思想对象的创建,由工厂负责生产特定对象。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

企业项目级IDEA设置类注释、方法注释模板(仅增加@author和@date)

文章目录 前言一 设置类注释1.1 添加模板1.2 复制配置 二 设置方法注释2.1 添加模版2.2 设置模版2.3 设置参数变量2.4 配置对应快捷键2.5 配置对应作用域2.6 使用方式 说明 前言 公司代码规范中&#xff0c;需要在标准JavaDoc注释的基础上加上作者和日期。网上虽然有很多现成的…

单片机学习笔记 2. LED灯闪烁

目录 0、实现的功能 1、Keil工程 2、代码实现 0、实现的功能 LED灯闪烁 1、Keil工程 闪烁原理&#xff1a;需要进行软件延时达到人眼能分辨出来的效果。常用的延时方法有软件延时和定时器延时。此次先进行软件延时 具体操作步骤和之前的笔记一致。此次主要利用无符号整型的范…

编辑器vim 命令的学习

1.编辑器Vim 1.vim是一个专注的编辑器 2.是一个支持多模式的编辑器 1.1见一见&#xff1a; vim 的本质也是一条命令 退出来&#xff1a;-> Shift:q 先创建一个文件 再打开这个文件 进入后先按 I 然后就可以输入了 输入完后&#xff0c;保存退出 按Esc --> 来到最后一…

调用门提权

在我写的2.保护模式&#xff0b;段探测这篇文章中&#xff0c;我们提到了S位对于段描述符的控制&#xff0c;之前我们已经介绍了代码段和数据段&#xff0c;现在我们来把目光转到系统段 在这么多中结构里面&#xff0c;我们今天要介绍的就是编号为12的&#xff0c;32位调用门 结…

langchain模型及I/O的封装

langchain安装&#xff1a;pip install langchain-openai https://python.langchain.com/v0.2/docs/integrations/platforms/openai/ 注意&#xff1a;安装后&#xff0c;我们需要在环境变量中配置OPENAI_API_KEY&#xff0c;langchain会自动获取 1.模型的封装 指令生成式模…

阿里斑马智行 2025届秋招 NLP算法工程师

文章目录 个人情况一面/技术面 1h二面/技术面 1h三面/HR面 20min 个人情况 先说一下个人情况&#xff1a; 学校情况&#xff1a;211本中9硕&#xff0c;本硕学校都一般&#xff0c;本硕都是计算机科班&#xff0c;但研究方向并不是NLP&#xff0c;而是图表示学习论文情况&…

谈一谈QThread::CurrentThread和this->thread

QThread::CurrentThread是指的当前函数调用者者所在的线程 this->thread是指的当前对象所在的线程&#xff08;对象创建出来的时候所在的线程&#xff09; Qt文档说明 CurrentThread返回一个指向管理当前执行线程的QThread的指针 thread返回对象所在的线程 这两个函数所…

深度学习实验十一 卷积神经网络(2)——基于LeNet实现手写体数字识别实验

目录 一、数据 二、模型构建 三、模型训练及评价 四、打印参数量和计算量 五、模型预测 附&#xff1a;完整可运行代码 实验大致步骤&#xff1a; 一、数据 下载网站&#xff1a;MNIST数据集 之前的官网不能下载数据集了&#xff0c;403了&#xff0c;所以找到一个类似…

Python语法便捷查询

一、Python基础语法&#xff1a; (1)注释&#xff1a; (2)标识符&#xff1a; 简介&#xff1a;标识符的格式限制和C语言一样 (3)字符串定义方法&#xff1a; (4)字符串拼接&#xff1a; (5)字符串的格式化&#xff08;占位拼接&#xff09;&#xff1a; 和C语言的printf类…

Ansys Maxwell - 3PH 感应电机 - 第 2 部分 - 机床工具包 ACT

本篇博文是“Ansys Maxwell&#xff1a;3PH 感应电机 - 力和热耦合”的延续。在本篇博文中&#xff0c;我将展示如何使用 Ansys Machine Toolkit ACT 开发扭矩与速度曲线&#xff08;一系列性能曲线&#xff0c;包括效率图&#xff09;&#xff0c;以评估在 Ansys Maxwell 中建…

【含开题报告+文档+PPT+源码】基于springboot的教师评价系统的设计与实现

开题报告 随着信息技术的迅猛发展&#xff0c;教育信息化已成为现代教育的必然趋势。教研室作为高校教学管理的重要机构&#xff0c;肩负着提升教学质量、推动教学改革的重要使命。然而&#xff0c;传统的教学管理方式往往存在效率低下、数据分散、管理不便等问题&#xff0c;…

用 Python 从零开始创建神经网络(八):梯度、偏导数和链式法则

梯度、偏导数和链式法则 引言1. 偏导数2. 和的偏导数3. 乘法的偏导数4. Max 的偏导数5. 梯度&#xff08;The Gradient&#xff09;6. 链式法则&#xff08;The Chain Rule&#xff09; 引言 在我们继续编写我们的神经网络代码之前&#xff0c;最后两个需要解决的难题是梯度和…

并查集 poj 2524,1611,1703,2236,2492,1988 练习集【蓝桥杯备赛】

目录 前言 并查集优势 Ubiquitous Religions poj 2524 问题描述 问题分析 代码 The Suspects poj 1611 问题描述 问题分析 代码 Wireless Network poj 2236 问题描述 问题分析 代码 分类 带权并查集合 权值树构建步骤 Find them, Catch them poj 1703 问题描述 问题分…

zabbix监控tomcat

1. 准备JDK环境 #vim /etc/profile export JAVA_HOME/usr/local/jdk export TOMCAT_HOME/usr/local/tomcat export PATH$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$TOMCAT_HOMOE/bin [rootCentOS8 ~]# source /etc/profile [rootCentOS8 ~]# java -version openjdk version &q…

Nuget For Unity插件介绍

NuGet for Unity&#xff1a;提升 Unity 开发效率的利器 NuGet 是 .NET 开发生态中不可或缺的包管理工具,你可以将其理解为Unity的Assets Store或者UPM,里面有很多库可以帮助我们提高开发效率。当你想使用一个库,恰好这个库没什么依赖(比如newtonjson),那么下载包并找到Dll直接…

如何在 Ubuntu 上安装 Mattermost 团队协作工具

简介 Mattermost 是一个开源、自托管的通信平台&#xff0c;专为团队协作设计。它类似于 Slack&#xff0c;提供聊天、消息传递和集成功能。Mattermost 在重视数据隐私的组织中特别受欢迎&#xff0c;因为它允许团队在自己的服务器上管理通信。以下是 Mattermost 的一些关键特…

初识Linux—— 基本指令(上)

前言 Linux简述 ​ Linux是一种开源、自由、类UNIX的操作系统&#xff0c;由著名的芬兰程序员林纳斯托瓦兹&#xff08;Linus Torvalds&#xff09;于1991年首次发布。Linux的内核在GNU通用公共许可证&#xff08;GPL&#xff09;下发布&#xff0c;这意味着任何人都可以自由…

VBA技术资料MF223:从文件添加新模块

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

利用RAGflow和LM Studio建立食品法规问答系统

前言 食品企业在管理标准、法规&#xff0c;特别是食品原料、特殊食品法规时&#xff0c;难以通过速查法规得到准确的结果。随着AI技术的发展&#xff0c;互联网上出现很多AI知识库的解决方案。 经过一轮测试&#xff0c;找到问题抓手、打通业务底层逻辑、对齐行业颗粒度、沉…

路径规划——RRT*算法

路径规划——RRT*算法 算法原理 RRT Star 算法是一种渐近最优的路径规划算法&#xff0c;它是 RRT 算法的优化版本。RRT Star 算法通过不断地迭代和优化&#xff0c;最终可以得到一条从起点到目标点的最优路径。 在学习RRT Star 算法之前最好先学习一下RRT原始算法&#xff1…