Java 与设计模式(14):策略模式

news2024/12/26 15:29:53

一、定义

策略模式(Strategy Pattern)是行为型设计模式之一,它定义了一系列算法,并将每一个算法封装起来,使它们可以互换使用,算法的变化不会影响使用算法的用户。策略模式让算法独立于使用它的客户而变化,从而避免使用多重条件判断语句。

策略模式的主要目的是将算法族封装在一起,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

策略模式通常包含以下角色:

  • 策略接口(Strategy Interface):定义所有支持的算法的公共接口。此接口通常由一组相关类实现,这些类提供了具体的算法实现。
  • 具体策略类(Concrete Strategy):实现策略接口,提供具体的算法实现。
  • 上下文(Context):维护一个策略类的引用,负责调用当前策略对象的算法方法。上下文不实现算法,而是依赖于策略对象去实现。
  • 客户端(Client):创建具体策略对象,并设置给上下文对象,从而影响上下文的行为。

策略模式的实现通常涉及以下步骤:

  1. 定义策略接口,声明一系列相关操作的公共方法。
  2. 实现具体策略类,每个类对应一种算法的实现。
  3. 创建上下文类,持有策略接口的引用,并提供方法来设置和获取当前的策略对象。
  4. 客户端代码根据需要创建具体策略对象,并将其设置给上下文,然后通过上下文来执行算法。

二、Java示例

下面这个示例展示了一个简单的支付系统,其中不同的支付策略(如信用卡支付、PayPal支付等)被封装在各自的策略类中。

首先,我们定义一个支付策略接口:

// 策略接口
public interface PaymentStrategy {
    void pay(int amount);
}

然后,我们实现具体的支付策略:

// 信用卡支付策略
public class CreditCardPaymentStrategy implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("Paying " + amount + " using Credit Card.");
    }
}

// PayPal支付策略
public class PayPalPaymentStrategy implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("Paying " + amount + " using PayPal.");
    }
}

接下来,我们创建上下文类,它将使用策略接口:

// 上下文类
public class PaymentContext {
    private PaymentStrategy strategy;

    // 设置支付策略
    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    // 执行支付
    public void executePayment(int amount) {
        strategy.pay(amount);
    }
}

最后,客户端代码可以这样使用策略模式:

public class Client {
    public static void main(String[] args) {
        PaymentContext context = new PaymentContext();

        // 使用信用卡支付
        context.setPaymentStrategy(new CreditCardPaymentStrategy());
        context.executePayment(1000);

        // 更改策略为PayPal支付
        context.setPaymentStrategy(new PayPalPaymentStrategy());
        context.executePayment(500);
    }
}

在这个示例中,PaymentContext 类是上下文,它维护一个对 PaymentStrategy 接口的引用。CreditCardPaymentStrategyPayPalPaymentStrategy 是具体的策略实现。客户端代码通过改变上下文的策略来改变支付方式,而不需要修改上下文或其他策略的代码。这样,支付方式的变化不会影响使用支付系统的其他部分,符合开闭原则。

三、优点

  • 算法的封装性:策略模式将算法封装在独立的策略类中,使得算法的变化不会影响到使用算法的客户端代码。
  • 代码的可维护性:当需要新增算法时,只需新增一个策略类实现策略接口,无需修改原有代码,符合开闭原则。
  • 算法的可替换性:客户端可以通过改变策略对象的选择来改变算法,使得算法可以灵活替换。
  • 提高算法的复用性:策略模式使得算法可以在不同的上下文中被重用,而不需要针对每个上下文单独编写算法。
  • 减少条件判断:策略模式避免了在客户端代码中使用多重条件判断来选择算法,使得代码更加清晰。

四、缺点

  • 客户端需要了解策略类:客户端需要了解不同策略类之间的区别,以便能够正确地使用它们。
  • 类的膨胀:如果策略模式使用不当,可能会导致系统中存在大量的策略类,使得系统变得复杂。
  • 所有策略类都需要实现接口:如果策略接口发生变化,所有实现该接口的策略类都需要进行相应的修改。
  • 可能存在性能问题:每次使用策略时都需要实例化一个策略对象,这可能会导致不必要的性能开销。
  • 策略类之间的共享数据问题:如果策略类之间需要共享数据,那么需要通过外部传入或者使用全局变量等方式来实现,这可能会使得代码的维护性降低。

五、使用场景

策略模式的常见使用场景包括:

  1. 多种算法的选择:当一个系统需要在运行时从多种算法中选择一种时,策略模式可以使得算法的变化独立于客户端。

  2. 算法族的封装:当存在一组相关的算法,并且这些算法可以相互替换时,策略模式可以将每个算法封装在单独的类中。

  3. 需要动态选择算法:如果算法的选择依赖于客户端输入或者某个运行时参数,策略模式可以在运行时动态地更改算法。

  4. 复杂的条件判断:在需要通过多重条件判断来选择执行不同逻辑的情况下,策略模式可以简化这些条件判断,使得代码更加清晰。

  5. 支付系统:如前述示例,不同的支付方式(信用卡、PayPal、微信支付等)可以作为不同的策略。

  6. 排序算法:在一个需要多种排序算法(如快速排序、归并排序、堆排序等)的系统中,每种排序算法可以作为一个策略。

  7. 图形渲染:在图形界面程序中,不同的图形对象可能需要不同的渲染策略。

  8. 数据库操作:在访问不同类型数据库(如MySQL、Oracle、MongoDB等)时,可以为每种数据库实现一个策略。

  9. 缓存策略:在缓存系统中,可以有多种缓存淘汰策略(如LRU、FIFO等),每种策略可以作为一个独立的策略类。

  10. 游戏AI:在游戏开发中,不同的角色可能有不同的AI行为(如追逐、逃避、攻击等),每种行为可以是一个策略。

  11. 用户界面:不同的用户界面元素可能需要不同的布局策略,如网格布局、流布局等。

  12. 折扣策略:在电子商务网站中,可以有多种折扣策略(如满减、打折、买一赠一等),每种策略可以作为一个独立的类。

  13. 文件格式转换:在需要支持多种文件格式转换的系统中,每种文件格式的转换可以作为一个策略。

  14. 数据加密:在需要多种加密算法(如AES、DES、RSA等)的系统中,每种加密算法可以作为一个策略。

  15. 策略游戏:在策略游戏中,不同的战术或策略可以作为不同的策略类。

六、注意事项

以下是一些需要避免的情况 :

  • 过度使用:策略模式并不是解决所有问题的万能钥匙。如果只有一种算法或者算法的选择不经常变化,使用策略模式可能会导致不必要的复杂性 。
  • 策略类与上下文类耦合:策略模式的目的是将算法的实现与使用算法的代码解耦。如果策略类与上下文类(Context)耦合得太紧密,就会失去策略模式的优势 。
  • 策略接口设计不当:策略接口应该定义清晰且一致的方法,以便不同的策略可以互换。如果接口设计得过于复杂或不一致,可能会导致策略难以实现或替换 。
  • 策略类太多:如果为每种可能的情况都创建一个策略类,可能会导致类的数量过多,增加维护成本。应该考虑是否有一些策略可以合并或者是否有一些策略可以通过参数配置来实现 。
  • 策略选择逻辑复杂:策略的选择逻辑应该简单明了。如果策略选择逻辑过于复杂,可能会使得代码难以理解和维护 。
  • 忽视策略的初始化:策略对象可能需要一些初始化数据才能正确工作。如果忽视了这一点,可能会导致策略对象在运行时出现错误 。
  • 忽视策略的销毁:如果策略对象持有资源(如数据库连接、文件句柄等),则需要确保这些资源在策略对象不再使用时被正确释放 。
  • 策略类的性能问题:策略类的性能可能会影响整个系统的性能。在设计策略时,应该考虑策略的性能,并在必要时进行优化 。
  • 策略类的状态管理:如果策略类持有状态,需要确保状态在策略切换时被正确处理。否则,可能会导致不一致的状态或错误 。
  • 策略类的错误处理:策略类应该能够处理错误,并在出现错误时提供清晰的反馈。如果忽视了错误处理,可能会导致系统在运行时出现不可预测的行为 。
  • 过度设计:不要为了使用策略模式而使用策略模式。在某些情况下,简单的条件判断可能比引入策略模式更加合适 。
  • 依赖过多:确保策略之间的依赖关系最小化,以保持每个策略的独立性和可互换性 。

七、在spring 中的应用

在Spring源码中,策略模式被应用在多个地方,以下是一些具体的使用场景:

  1. 版本资源解析
    在Spring MVC中,处理静态资源时,使用了策略模式来定义不同的版本资源解析策略。例如,VersionPathStrategy 接口定义了如何从请求路径中提取和嵌入资源版本的方法。具体的策略实现类,如 FixedVersionStrategyContentVersionStrategy,封装了不同的版本解析逻辑。

  2. Bean定义解析
    Spring在解析Bean定义时,使用了策略模式。BeanDefinitionParser 接口定义了解析Bean定义的策略,不同的解析器实现类负责解析不同类型的Bean定义。

  3. 类型过滤
    在Spring的组件扫描和配置中,TypeFilter 接口用于决定哪些类型的Bean定义应该被包含或排除。实现这个接口的不同类提供了不同的过滤策略。

  4. 导入选择器和注册器
    在Spring的配置类处理中,ImportSelectorImportBeanDefinitionRegistrar 接口使用了策略模式。这些接口允许Spring根据不同的策略动态地导入配置类或注册Bean定义。

  5. 环境抽象
    Spring的 Environment 抽象使用了策略模式,通过不同的 PropertySource 实现类来提供不同来源的属性值。

  6. 资源加载
    ResourceLoader 接口及其实现类使用了策略模式,以便根据不同的资源类型(如文件系统资源、类路径资源等)加载资源。

  7. 事务管理
    Spring的事务管理器使用了策略模式,通过不同的事务管理器实现类来提供不同的事务管理策略,如编程式事务和声明式事务。

  8. 解析策略工具类
    ParserStrategyUtils 类是一个解析策略工具类,它使用策略模式来处理不同类型的解析任务,如Bean定义解析、环境解析等。

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

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

相关文章

Java Map

Map——广义集合的子集 HashTable是早期Java类库提供的一个哈希表实现,扩展了Dictionary类,类结构上与HashMap明显不同,本身是同步的,不支持null键和值,由于同步导致的性能开销,已经很少被推荐使用。 Hash…

【MySQL — 数据库基础】MySQL的安装与配置 & 数据库简单介绍

数据库基础 本节目标 掌握关系型数据库,数据库的作用掌握在Windows和Linux系统下安装MySQL数据库了解客户端工具的基本使用和SQL分类了解MySQL架构和存储引擎 1. 数据库的安装与配置 1.1 确认MYSQL版本 处理无法在 cmd 中使用 mysql 命令的情况&a…

实测数据处理(BP算法处理)——SAR成像算法系列(九)

系列文章目录 《SAR学习笔记-SAR成像算法系列(一)》 《后向投影算法(BPA)-SAR成像算法系列(二)》 《后向投影算法(续)-SAR成像算法系列(八)》 文章目录 一…

(数据结构与算法)如何提高学习算法的效率?面试算法重点有哪些?面试需要哪些能力?

面试官眼中的求职者 通过对你算法的考察!!!! 缩进太多!!一般不要超过三层!!!缩进越少,bug越少;逻辑比较复杂,把这些包装成为函数&…

Day05:缓存双写一致性

redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性–强一致) 一种是一致性要求比较高的同步方案,另一种是允许延迟一致的异步通知。 什么是双写一致性? 双写一致性:当修改了数据库…

vue3+typescript自定义input组件

官方文档:https://cn.vuejs.org/guide/components/events#%E5%AE%9A%E4%B9%89%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6 触发与监听事件​ 在组件的模板表达式中,可以直接使用 $emit 方法触发自定义事件 (例如:在 v-on 的处理函数中)…

代码之丑第一期-缩进

各位小伙伴们,大家好!咱今天就算是正式开张了。实不相瞒,第一期的内容早已写好,但唯独这开篇方式,笔者想了好些时间,包括但不限于如下风格: 斗破苍穹式(已经三刷):代码优雅之力,三段!级别:低级!百年孤独式(困扰于错综复杂的人物关系,放弃):多年以后,面对吐槽…

idea2024加载flowable6.8.1.36遇到的问题-idea启动flowable问题flowable源码启动问题

代码下载地址: https://gitee.com/hanpenghu_admin_admin/flowable6.8.1.git 1.首先是通过顶层目录maven clean install 发现很多子模块并不会install本地mavenStore库,这导致了,一堆相互依赖的模块报错找不到,所以需要根据报错…

Vue.js 中 v-for 指令的三种常见用法详解及key、value、id的作用

作者:CSDN-PleaSure乐事 欢迎大家阅读我的博客 希望大家喜欢 使用环境:WebStorm 目录 遍历数组 介绍 代码 遍历对象数组 介绍 代码 遍历对象本身 介绍 代码 效果呈现 key、value、id的作用 1. value 2. key 3. id 在 Vue.js 中&#xff0c…

【论文投稿】国产游戏技术:迈向全球引领者的征途

【IEEE出版南方科技大学】第十一届电气工程与自动化国际会议(IFEEA 2024)_艾思科蓝_学术一站式服务平台 更多学术会议论文投稿请看:https://ais.cn/u/nuyAF3 目录 国产游戏技术能否引领全球? 一、国产游戏技术的崛起之路 1.1 初期探索与积…

React的ts文件中通过createElement拼接一段内容出来

比如接口返回一个值 const values [23.00, 40.00/kg];想做到如下效果, 如果单纯的用render渲染会很简单, 但是在ts文件中处理,所以采用了createElement拼接 代码如下: format: (values: string[]) > {if (!values || !val…

江协科技最新OLED保姆级移植hal库

江协科技最新OLED移植到hal库保姆级步骤 源码工程存档 工程和源码下载(密码 1i8y) 原因 江协科技的开源OLED封装的非常完美, 可以满足我们日常的大部分开发, 如果可以用在hal库 ,将是如虎添翼, 为我们开发调试又增加一个新的瑞士军刀, 所以我们接下来手把手的去官网移植源码…

HarmonyOS:使用Emitter进行线程间通信

Emitter主要提供线程间发送和处理事件的能力,包括对持续订阅事件或单次订阅事件的处理、取消订阅事件、发送事件到事件队列等。 一、Emitter的开发步骤如下: 订阅事件 import { emitter } from kit.BasicServicesKit; import { promptAction } from kit.…

Wi-Fi 进化论:从过去到未来(6/10)

Wi-Fi(发音: /ˈwaɪfaɪ/),在中文里又称作“移动热点”,是Wi-Fi联盟制造商的商标作为产品的品牌认证,是基于IEEE 802.11标准的无线局域网通信技术 [6]。基于两套系统的密切相关,也常有人把Wi-F…

【C++初阶】第5课—动态内存管理

文章目录 1. 内存分布2. C语言动态内存管理3. C内存管理方式3.1 new/delete操作内置类型3.2 new和delete操作自定义类型 4. operator new和operator delete函数5. new和delete的实现原理6. malloc/free和new/delete的区别7. 定位new表达式(了解即可) 1. 内存分布 先来做一个关于…

学习threejs,设置envMap环境贴图创建反光效果

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.CubeTextureLoader 立…

Matlab Simulink HDL Coder FPGA开发初体验—计数器

目录 一、Simulink设计及仿真二、Verilog HDL代码转换1、参数配置2、HDL代码生成 三、ModelSim仿真分析1、使用自己编写的Testbench文件进行仿真2、使用HDL Coder生成的Testbench文件进行仿真 前言 Simulink HDL Coder‌是一款将Simulink和Stateflow模型转化为可综合的Verilog和…

【C语言】扫雷游戏(一)

我们先设计一个简单的9*9棋盘并有10个雷的扫雷游戏。 1,可以用数组存放,如果有雷就用1表示,没雷就用0表示。 2,排查(2,5)这个坐标时,我们访问周围的⼀圈8个位置黄色统计周围雷的个数是1。排查(8,6)这个坐标时&#xf…

实现点名神器的pyqt6实现

利用python gui创建点名神器,包含加分、导出加分记录、清除加分记录。 点名页面 首先导入学生信息 导入成功 开始点名 点击停止 点过之后,点击加分 加完分 查看加分记录 可以直接进入导出记录和清除历史。 此文到此结束,想要源码的请私聊我&a…

【UE5 C++】判断两点连线是否穿过球体

目录 前言 原理 代码 测试 结果 前言 通过数学原理判断空间中任意两点的连线是否穿过球体,再通过射线检测检验算法的正确性。 原理 (1)设球体球心的坐标为 ,半径为r; (2)设线段中A点的坐…