Java 设计模式之策略模式 (Strategy Pattern) 详解
Java 设计模式之策略模式 (Strategy Pattern) 详解
策略模式(Strategy Pattern)是一种行为型设计模式,旨在定义一系列算法,将每个算法封装起来,并使它们可以互相替换,从而使得算法的变化不会影响使用算法的客户端。策略模式的主要结构包括策略接口、具体策略类和上下文类,通过将算法的选择与使用分离,实现了代码的可维护性和灵活性。
更多设计模式请参考:
Java 中的 23 种设计模式详解
1. 策略模式的动机
在软件开发中,经常遇到需要在运行时动态选择一种算法的情况。例如,排序算法、支付方式、文件压缩等场景都可能需要在不同条件下选择不同的算法实现。如果在客户端代码中硬编码这些算法的选择逻辑,会导致代码难以维护和扩展。策略模式通过将算法的选择和实现分离,使得算法可以独立变化,客户端代码可以更简洁和灵活。
2. 策略模式的结构
策略模式包含以下几部分:
- 策略接口(Strategy Interface):定义所有支持的算法的公共接口。
- 具体策略类(Concrete Strategies):实现策略接口,定义具体的算法。
- 上下文类(Context Class):使用一个具体策略对象来配置,并维护对策略对象的引用。
3. 策略模式的UML类图
4. 策略模式的实现
以下是一个使用策略模式的Java示例,该示例演示了如何选择不同的策略来执行操作:
4.1 策略接口
// 定义策略接口
public interface Strategy {void execute();
}
4.2 具体策略类
// 具体策略A
public class ConcreteStrategyA implements Strategy {@Overridepublic void execute() {System.out.println("执行策略A");}
}// 具体策略B
public class ConcreteStrategyB implements Strategy {@Overridepublic void execute() {System.out.println("执行策略B");}
}
4.3 上下文类
// 上下文类
public class Context {private Strategy strategy;// 设置策略public void setStrategy(Strategy strategy) {this.strategy = strategy;}// 执行策略public void executeStrategy() {if (strategy == null) {throw new IllegalStateException("Strategy未设置");}strategy.execute();}
}
4.4 客户端代码
public class StrategyPatternDemo {public static void main(String[] args) {Context context = new Context();// 使用策略Acontext.setStrategy(new ConcreteStrategyA());context.executeStrategy(); // 输出: 执行策略A// 使用策略Bcontext.setStrategy(new ConcreteStrategyB());context.executeStrategy(); // 输出: 执行策略B}
}
5. 策略模式的优缺点
优点
- 算法可以自由切换:可以在不影响客户端的情况下更改算法。
- 避免多重条件判断:使用策略模式可以避免过多的if-else或switch-case语句。
- 扩展性好:增加新的策略时只需添加新的策略类即可,不需要修改现有代码。
缺点
- 客户端必须知道所有的策略类:客户端需要了解每个策略类的具体实现,这增加了复杂度。
- 增加对象数目:如果策略较多,会增加类的数量,导致系统变得复杂。
6. 策略模式的应用场景
策略模式适用于以下场景:
- 需要在不同情况下使用不同的算法。
- 有许多相关类仅仅在行为上有所不同。
- 需要避免使用复杂的条件语句来选择不同的行为。
7. 策略模式的变体
策略模式可以与其他设计模式结合使用,以增强其功能。例如:
- 组合模式(Composite Pattern):可以将策略模式与组合模式结合,使得策略的选择更加灵活。
- 工厂模式(Factory Pattern):可以使用工厂模式来创建策略对象,从而实现策略的动态选择。
8. 策略模式与其他设计模式的比较
- 策略模式 vs. 状态模式:两者结构类似,但策略模式的不同策略是彼此独立的,而状态模式的不同状态之间存在一定的关系。
- 策略模式 vs. 命令模式:命令模式用于封装请求,将请求与执行解耦,而策略模式用于封装算法,将算法与使用算法的代码解耦。
9. 策略模式的实现细节与最佳实践
9.1 延迟初始化策略
在某些情况下,策略的初始化可能比较耗时,可以使用延迟初始化(Lazy Initialization)来提高性能:
public class Context {private Strategy strategy;public void setStrategy(Strategy strategy) {this.strategy = strategy;}public void executeStrategy() {if (strategy == null) {// 延迟初始化strategy = new ConcreteStrategyA();}strategy.execute();}
}
9.2 使用反射动态加载策略
为了避免频繁修改代码,可以通过反射动态加载策略:
public class Context {private Strategy strategy;public void setStrategy(String strategyClassName) throws Exception {this.strategy = (Strategy) Class.forName(strategyClassName).getDeclaredConstructor().newInstance();}public void executeStrategy() {strategy.execute();}
}
9.3 使用配置文件管理策略
将策略的配置放在配置文件中,便于管理和维护:
# strategy.properties
strategy=ConcreteStrategyA
import java.io.InputStream;
import java.util.Properties;public class StrategyLoader {public static Strategy loadStrategy() throws Exception {Properties properties = new Properties();try (InputStream input = StrategyLoader.class.getResourceAsStream("/strategy.properties")) {properties.load(input);}String strategyClassName = properties.getProperty("strategy");return (Strategy) Class.forName(strategyClassName).getDeclaredConstructor().newInstance();}
}
9.4 策略模式与依赖注入
结合依赖注入框架(如Spring),可以更加灵活地管理策略的实例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class Context {private final Strategy strategy;@Autowiredpublic Context(Strategy strategy) {this.strategy = strategy;}public void executeStrategy() {strategy.execute();}
}
10. 策略模式的实际应用案例
10.1 支付系统中的策略模式
在一个支付系统中,可能有多种支付方式,如信用卡支付、支付宝支付、微信支付等。通过策略模式,可以根据用户选择的支付方式动态切换支付策略。
支付策略接口
public interface PaymentStrategy {void pay(double amount);
}
具体支付策略类
// 信用卡支付策略
public class CreditCardPaymentStrategy implements PaymentStrategy {@Overridepublic void pay(double amount) {System.out.println("使用信用卡支付:" + amount + "元");}
}// 支付宝支付策略
public class AliPayPaymentStrategy implements PaymentStrategy {@Overridepublic void pay(double amount) {System.out.println("使用支付宝支付:" + amount + "元");}
}// 微信支付策略
public class WeChatPaymentStrategy implements PaymentStrategy {@Overridepublic void pay(double amount) {System.out.println("使用微信支付:" + amount + "元");}
}
支付上下文类
public class PaymentContext {private PaymentStrategy paymentStrategy;// 设置支付策略public void setPaymentStrategy(PaymentStrategy paymentStrategy) {this.paymentStrategy = paymentStrategy;}// 执行支付public void pay(double amount) {if (paymentStrategy == null) {throw new IllegalStateException("PaymentStrategy未设置");}paymentStrategy.pay(amount);}
}
支付策略工厂类
为了更加优雅地创建支付策略,可以使用工厂模式:
public class PaymentStrategyFactory {public static PaymentStrategy getPaymentStrategy(String type) {switch (type) {case "CreditCard":return new CreditCardPaymentStrategy();case "AliPay":return new AliPayPaymentStrategy();case "WeChat":return new WeChatPaymentStrategy();default:throw new IllegalArgumentException("未知的支付类型: " + type);}}
}
客户端代码
public class PaymentDemo {public static void main(String[] args) {PaymentContext context = new PaymentContext();// 从外部获取支付类型,例如通过用户输入或配置文件String paymentType = "CreditCard"; // 这里可以根据实际情况更改// 使用工厂创建支付策略PaymentStrategy paymentStrategy = PaymentStrategyFactory.getPaymentStrategy(paymentType);// 设置支付策略context.setPaymentStrategy(paymentStrategy);// 执行支付context.pay(100.0); // 输出: 使用信用卡支付:100.0元// 更改支付策略paymentType = "AliPay";paymentStrategy = PaymentStrategyFactory.getPaymentStrategy(paymentType);context.setPaymentStrategy(paymentStrategy);context.pay(200.0); // 输出: 使用支付宝支付:200.0元// 更改支付策略paymentType = "WeChat";paymentStrategy = PaymentStrategyFactory.getPaymentStrategy(paymentType);context.setPaymentStrategy(paymentStrategy);context.pay(300.0); // 输出: 使用微信支付:300.0元}
}
优化的重点
- 工厂模式:使用工厂模式来创建支付策略对象,使客户端代码更简洁,策略的创建和选择更灵活。
- 空策略检查:在上下文类中增加对策略是否为空的检查,避免未设置策略时的运行时错误。
- 策略类型动态获取:通过从外部(如用户输入或配置文件)获取支付类型,示例代码更加接近实际应用场景。
通过策略模式和工厂模式的结合,可以实现一个灵活、可扩展且易于维护的支付系统。在实际开发中,进一步结合依赖注入框架(如Spring)来管理策略对象,可以提升代码的可测试性和可扩展性。
相关文章:

Java 设计模式之策略模式 (Strategy Pattern) 详解
Java 设计模式之策略模式 (Strategy Pattern) 详解 策略模式(Strategy Pattern)是一种行为型设计模式,旨在定义一系列算法,将每个算法封装起来,并使它们可以互相替换,从而使得算法的变化不会影响使用算法的…...
习题20240803(未完成)
文章目录 一、Linq练习 使用Linq完成下面练习1.题目: 返回 numbers 列表中的所有数字。2.题目: 返回 numbers 列表中的所有偶数。3.题目: 返回 numbers 列表中所有大于10的数字。4.题目: 返回 students 列表中所有学生的姓名。5.题目: 返回 numbers 列表按升序排序后的数字。6.…...

C语言程序设计25
《C程序设计教程(第四版)——谭浩强》 习题2.2 分析下面程序的运行结果,然后上机验证。 代码: //《C程序设计教程(第四版)——谭浩强》 //习题2.2 分析下面程序的运行结果,然后上机验证。#inc…...

TypeScript 基础类型与类型声明
前言 在 JavaScript 中,变量是没有类型的,变量的值的类型是在运行时确定的,这被称为动态类型。 这意味着可以在不同的时间将不同类型的值赋给同一个变量,并且 JavaScript 会在运行时根据当前赋给变量的值来确定其类型。 示例&…...

算法:BFS 解决多源最短路问题
目录 多源最短路 题目一:矩阵 题目二:飞地的数量 题目三:地图中的最高点 题目四:地图分析 多源最短路 首先想要知道多源最短路,就先要明白单源最短路,bfs解决单源最短路问题前面学习过,单…...

grep工具的使用
grep [options]…… pattern [file]…… 工作方式: grep 在一个或者多个文件中搜索字符串模板,如果模板中包括空格,需要使用引号引起来,模 板后的所有字符串会被看作是文件名。 工作结果:如果模板搜索成功…...
Langchain核心模块与实战[9]:RAG检索增强生成[文本向量化、实战ChatDoc智能文档助手]
Langchain核心模块与实战[9]:RAG检索增强生成[文本向量化、实战ChatDoc智能文档助手] 参考文章可以使用国产LLM进行下述项目复现: 初识langchain[1]:Langchain实战教学,利用qwen2.1与GLM-4大模型构建智能解决方案[含Agent、tavily面向AI搜索]langchain[2]:Langchain实战教…...

Java从入门到精通(十五) ~ IO流
晚上好,愿这深深的夜色给你带来安宁,让温馨的夜晚抚平你一天的疲惫,美好的梦想在这个寂静的夜晚悄悄成长。 目录 前言 什么是IO流? IO流的作用: 一、基础流 1. 字节流 1.1 字节输入流 FileInputStream 1.2 字节…...

C Primer Plus 第4章——第二篇
你该逆袭了 第4章:重点摘录 五、scanf( )1、使用 scanf( )(1)转换说明 *(2)转换说明 数字(3)转换说明 hh(4)scanf 中其他的转换说明,不作详细解释,用到的时候再去学习即可 2、从 scanf( ) 角度 看 输入3、格式字符串中的普通字符4、scanf&…...
优化海外用户体验,畅通支付路径!来了解WeTest的本地化支付测试方案
在APP出海的全生命周期中,支付系统的稳定运行是至关重要的一环。随着产品服务覆盖地区的拓展、APP内付费功能的拓展以及不同地区用户对多样化支付渠道的需求增加,出海APP的当地支付体验的优劣直接影响到海外用户的消费决策。 然而海外支付风控升级&#…...
VUE框架面试整理-模板语法
Vue.js 的模板语法允许你声明式地将数据绑定到 DOM。以下是一些常见的模板语法和用法: 插值 插值语法用于在 HTML 中插入数据。 <p>{{ message }}</p>data:...

【C语言】fseek、ftell以及rewind函数(随机文件读写)
文章目录 前言1. fseek1.1 fseek函数原型1.2 fseek函数的形式参数1.3 fseek实例演示 2. ftell2.1 ftell函数原型2.2 ftell函数的实例演示 3. rewind3.1 rewind函数原型3.2 rewind函数实例演示 前言 在之前,我讲过文件的顺序读写。但是我们可不可以随机读写文件呢&a…...

使用 Elastic Observability 中的 OpenTelemetry 进行基础设施监控
作者:来自 Elastic ISHLEEN KAUR 将 OpenTelemetry 与 Elastic Observability 相结合,形成应用程序和基础设施监控解决方案。 在 Elastic,我们最近决定全面采用 OpenTelemetry 作为首要的数据收集框架。作为一名可观察性工程师,我…...

征服数据结构中的时间和空间复杂度
目录 时间复杂度推导大O方法求解时间复杂度的方法普通顺序结构单循环双循环递归Master定理(主定理)递归树方法 空间复杂度 一个算法的好坏根据什么来判断呢?有两种一种是时间效率,一种是空间效率。时间效率也可称为时间复杂度&…...
springboot Security vue
在使用Spring Boot Security与Vue.js构建前后端分离的应用时,你需要处理几个关键的技术点,包括认证(Authentication)和授权(Authorization),以及如何处理跨域请求(CORS)、…...
13. 计算机网络HTTPS协议(一)
1. 前言 在上一章节中我们介绍了 HTTP 协议相关的面试题目,作为 HTTP 协议的扩展,HTTPS 协议也经常被面试官提起。 因为对于大部分的前端、后端开发者,都接触不到 HTTPS 协议的开发场景,因为我们往往只关注请求路径后缀,例如关注 URL: /get/username,而非路径全称 htt…...

Bean的作用域和生命周期
Bean的作用域 我们先来看下面这段代码 首先是一个Dog类 (此处使用lombok来完成setter、getter、toString方法) Setter Getter public class Dog {private String name;} 然后在DogBeanConfig类里面写一个返回Dog的方法,并将这个方法的返…...

【Qt】QMainWindow之菜单栏
目录 一.菜单栏 1.概念 2.组成 二.代码创建菜单栏 1.创建菜单栏 2.在菜单栏中添加菜单 3.在菜单中添加菜单项 三.图形化创建菜单栏 1.在打开Qt自带的ui文件界面后,得到以下界面 2.双击点击界面中(在这里输入),在菜单栏中进行…...

uni-app封装组件实现下方滑动弹出模态框
子组件 <template><div class"bottom-modal" :class"{show: showModal}"><div class"modal-content" :class"{show: showModal}"><!-- 内容区域 --><slot></slot></div></div></…...

MATLAB(15)分类模型
一、前言 在MATLAB中,实现不同类型的聚类(如K-means聚类、层次聚类、模糊聚类)和分类(如神经网络分类)需要用到不同的函数和工具箱。下面我将为每种方法提供一个基本的示例代码。 二、实现 1. K-means聚类 % 假设X是…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

VisualXML全新升级 | 新增数据库编辑功能
VisualXML是一个功能强大的网络总线设计工具,专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑(如DBC、LDF、ARXML、HEX等),并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...
Python实现简单音频数据压缩与解压算法
Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中,压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言,提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...

基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)
引言 在嵌入式系统中,用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例,介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单,执行相应操作,并提供平滑的滚动动画效果。 本文设计了一个…...
《Offer来了:Java面试核心知识点精讲》大纲
文章目录 一、《Offer来了:Java面试核心知识点精讲》的典型大纲框架Java基础并发编程JVM原理数据库与缓存分布式架构系统设计二、《Offer来了:Java面试核心知识点精讲(原理篇)》技术文章大纲核心主题:Java基础原理与面试高频考点Java虚拟机(JVM)原理Java并发编程原理Jav…...

医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor
1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...