设计模式之十四:策略模式详解及其在Spring与Java中的应用

引言

在软件开发中,我们经常会遇到这样的情况:实现同一个功能有多种算法或策略,而我们需要根据不同的情况选择不同的实现方式。传统的做法是将这些算法硬编码在业务逻辑中,通过大量的if-else或switch-case来判断使用哪种算法。这种做法不仅导致代码臃肿,而且难以维护和扩展。

策略模式(Strategy Pattern)正是为解决这类问题而生。本文将详细介绍策略模式的定义、结构、实现方式,并结合Java实际案例,探讨如何在Spring框架中优雅地应用策略模式。

一、策略模式概述

1.1 定义与核心思想

策略模式定义了一系列的算法,并将每一个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。

核心思想:针对接口编程,而不是针对实现编程。将可变的部分从程序中抽象分离,形成算法的独立族,而在上下文(Context)中委派给具体的策略实现。

1.2 模式结构

策略模式包含三个核心角色:

  1. Context(上下文):持有一个策略对象的引用,负责调用策略对象的算法。
  2. Strategy(抽象策略):定义所有策略类必须实现的公共接口。
  3. ConcreteStrategy(具体策略):实现抽象策略接口,提供具体的算法实现。

1.3 类图

+---------------+ +-------------------+ | Context | | <<interface>> | +---------------+ | Strategy | | - strategy |------>+-------------------+ +---------------+ | + algorithm() | | + execute() | +-------------------+ +---------------+ ▲ | +---------------+---------------+ | | | +-----------+----+ +-----------+----+ +-----------+----+ |ConcreteStrategyA| |ConcreteStrategyB| |ConcreteStrategyC| +----------------+ +----------------+ +----------------+ | + algorithm() | | + algorithm() | | + algorithm() | +----------------+ +----------------+ +----------------+ 

二、策略模式的Java实现示例

让我们通过一个电商促销活动的例子来演示策略模式的实现。

场景描述

一个电商平台需要支持多种促销策略:

  • 满减促销
  • 折扣促销
  • 无促销(原价)

2.1 定义抽象策略接口

/** * 促销策略接口 */publicinterfacePromotionStrategy{/** * 计算促销后的价格 * @param originalPrice 原价 * @return 促销后价格 */doublecalculatePrice(double originalPrice);/** * 获取促销名称 * @return 促销名称 */StringgetPromotionName();}

2.2 实现具体策略类

满减策略:满100减20

publicclassFullReductionStrategyimplementsPromotionStrategy{privatestaticfinaldouble FULL_AMOUNT =100;privatestaticfinaldouble REDUCTION_AMOUNT =20;@OverridepublicdoublecalculatePrice(double originalPrice){if(originalPrice >= FULL_AMOUNT){return originalPrice - REDUCTION_AMOUNT;}return originalPrice;}@OverridepublicStringgetPromotionName(){return"满减促销";}}

折扣策略:8折优惠

publicclassDiscountStrategyimplementsPromotionStrategy{privatestaticfinaldouble DISCOUNT_RATE =0.8;@OverridepublicdoublecalculatePrice(double originalPrice){return originalPrice * DISCOUNT_RATE;}@OverridepublicStringgetPromotionName(){return"折扣促销";}}

无促销策略:原价

publicclassNoPromotionStrategyimplementsPromotionStrategy{@OverridepublicdoublecalculatePrice(double originalPrice){return originalPrice;}@OverridepublicStringgetPromotionName(){return"无促销";}}

2.3 创建上下文类

/** * 购物车 - 上下文角色 */publicclassShoppingCart{privatePromotionStrategy promotionStrategy;privateList<Double> items =newArrayList<>();publicShoppingCart(){this.promotionStrategy =newNoPromotionStrategy();// 默认无促销}publicvoidsetPromotionStrategy(PromotionStrategy promotionStrategy){this.promotionStrategy = promotionStrategy;}publicvoidaddItem(double price){ items.add(price);}publicdoublecalculateTotal(){double total = items.stream().mapToDouble(Double::doubleValue).sum();return promotionStrategy.calculatePrice(total);}publicvoidcheckout(){double finalPrice =calculateTotal();System.out.println("使用"+ promotionStrategy.getPromotionName()+",应付金额:"+ finalPrice);}}

2.4 客户端使用示例

publicclassClient{publicstaticvoidmain(String[] args){ShoppingCart cart =newShoppingCart(); cart.addItem(80); cart.addItem(30);// 使用满减促销 cart.setPromotionStrategy(newFullReductionStrategy()); cart.checkout();// 使用折扣促销 cart.setPromotionStrategy(newDiscountStrategy()); cart.checkout();// 无促销 cart.setPromotionStrategy(newNoPromotionStrategy()); cart.checkout();}}

输出结果:

使用满减促销,应付金额:90.0 使用折扣促销,应付金额:88.0 使用无促销,应付金额:110.0 

三、策略模式在Spring框架中的应用

Spring框架中大量使用了策略模式,下面介绍几个典型的应用场景。

3.1 ResourceLoader - 资源加载策略

Spring的ResourceLoader接口使用了策略模式来加载不同类型的资源。

publicinterfaceResourceLoader{ResourcegetResource(String location);}// 不同协议的资源加载策略// ClassPathResource// FileSystemResource// UrlResource

3.2 InstantiationStrategy - Bean实例化策略

Spring在创建Bean实例时,使用了不同的实例化策略。

publicinterfaceInstantiationStrategy{Objectinstantiate(RootBeanDefinition bd,String beanName,BeanFactory owner);// ... 其他方法}// 具体策略实现// SimpleInstantiationStrategy - 简单实例化// CglibSubclassingInstantiationStrategy - CGLIB动态代理实例化

3.3 在Spring Boot中自定义策略模式的应用

让我们看一个在Spring Boot项目中优雅实现策略模式的例子。

场景:用户支付方式选择

// 1. 定义策略接口publicinterfacePaymentStrategy{StringgetPaymentType();PayResultpay(PayRequest request);}// 2. 定义策略注解@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public@interfacePaymentType{Stringvalue();}// 3. 实现具体策略@Component@PaymentType("alipay")publicclassAlipayStrategyimplementsPaymentStrategy{@OverridepublicStringgetPaymentType(){return"alipay";}@OverridepublicPayResultpay(PayRequest request){System.out.println("使用支付宝支付:"+ request.getAmount());// 支付宝支付逻辑returnnewPayResult(true,"支付宝支付成功");}}@Component@PaymentType("wechat")publicclassWechatPayStrategyimplementsPaymentStrategy{@OverridepublicStringgetPaymentType(){return"wechat";}@OverridepublicPayResultpay(PayRequest request){System.out.println("使用微信支付:"+ request.getAmount());// 微信支付逻辑returnnewPayResult(true,"微信支付成功");}}@Component@PaymentType("card")publicclassCardPayStrategyimplementsPaymentStrategy{@OverridepublicStringgetPaymentType(){return"card";}@OverridepublicPayResultpay(PayRequest request){System.out.println("使用银行卡支付:"+ request.getAmount());// 银行卡支付逻辑returnnewPayResult(true,"银行卡支付成功");}}// 4. 策略工厂 - 注入所有策略@ComponentpublicclassPaymentStrategyFactoryimplementsApplicationContextAware{privatefinalMap<String,PaymentStrategy> strategyMap =newHashMap<>();@OverridepublicvoidsetApplicationContext(ApplicationContext applicationContext)throwsBeansException{Map<String,Object> beans = applicationContext.getBeansWithAnnotation(PaymentType.class);for(Object bean : beans.values()){PaymentStrategy strategy =(PaymentStrategy) bean;PaymentType paymentType = strategy.getClass().getAnnotation(PaymentType.class); strategyMap.put(paymentType.value(), strategy);}}publicPaymentStrategygetStrategy(String paymentType){PaymentStrategy strategy = strategyMap.get(paymentType);if(strategy ==null){thrownewIllegalArgumentException("不支持的支付类型:"+ paymentType);}return strategy;}}// 5. 服务类使用@ServicepublicclassPaymentService{@AutowiredprivatePaymentStrategyFactory strategyFactory;publicPayResultprocessPayment(String paymentType,PayRequest request){PaymentStrategy strategy = strategyFactory.getStrategy(paymentType);return strategy.pay(request);}}

四、策略模式的优缺点

4.1 优点

  1. 开闭原则:可以在不修改原有代码的情况下,增加新的策略。
  2. 避免多重条件语句:消除大量的if-else或switch-case。
  3. 提高算法的复用性:策略可以被不同的上下文复用。
  4. 策略可以自由切换:由于策略类实现同一个接口,它们可以自由切换。

4.2 缺点

  1. 类数量增加:每个策略都是一个类,可能会产生很多策略类。
  2. 客户端必须了解策略:客户端需要知道有哪些策略可选,并选择合适的策略。
  3. 策略类之间无法共享数据:除非使用上下文传递数据。

五、使用总结与实践建议

5.1 适用场景

  • 系统中有多个类,它们之间的区别仅在于行为不同。
  • 需要动态选择算法或策略的场景。
  • 算法需要使用if-else或switch-case进行选择的场景。
  • 需要屏蔽算法实现细节的场景。

5.2 最佳实践

  1. 结合工厂模式:使用工厂模式创建策略对象,减少客户端对具体策略的依赖。
  2. 使用枚举定义策略类型:在策略工厂中使用枚举管理策略类型,提高代码可读性。
  3. Spring中利用依赖注入:充分利用Spring的IOC容器管理策略Bean,通过注解自动注册。
  4. 策略与状态模式的区别
    • 策略模式:客户端主动选择策略,策略之间相互独立。
    • 状态模式:状态随上下文变化自动切换,状态之间有关联。

5.3 与Spring Boot的完美结合

在Spring Boot项目中,策略模式的实现可以更优雅:

// 使用枚举定义策略类型publicenumPaymentTypeEnum{ALIPAY("alipay","支付宝"),WECHAT("wechat","微信支付"),CARD("card","银行卡");privateString code;privateString desc;PaymentTypeEnum(String code,String desc){this.code = code;this.desc = desc;}publicStringgetCode(){return code;}}// 结合Optional处理空值publicPayResultprocessPayment(String paymentType,PayRequest request){returnOptional.ofNullable(strategyFactory.getStrategy(paymentType)).map(strategy -> strategy.pay(request)).orElseThrow(()->newIllegalArgumentException("不支持的支付类型"));}

结语

策略模式是一种非常实用且常用的设计模式,它体现了"封装变化"的设计原则。在实际开发中,合理运用策略模式可以让代码更加优雅、易于维护和扩展。特别是在Spring框架的支持下,策略模式的实现变得更加简单和高效。

Read more

Day 93:【99天精通Python】终极项目 - AI 聊天机器人 (下) - 前端界面与部署

Day 93:【99天精通Python】终极项目 - AI 聊天机器人 (下) - 前端界面与部署 前言 欢迎来到第93天! 在过去的两个章节中,我们已经搭建了一个功能强大的后端: * 支持流式对话和多轮记忆。 * 支持上传 PDF 作为知识库进行 RAG 问答。 今天,我们要为这个强大的"大脑"配上一副漂亮的"面孔"。我们将使用原生 HTML, CSS, JavaScript 编写一个聊天界面,并学习如何将整个项目部署上线。 本节内容: * 聊天界面 HTML 结构 * CSS 美化 * JavaScript 核心逻辑 (EventSource) * 文件上传交互 * 项目最终部署 一、HTML 骨架 (templates/

By Ne0inhk

Python小白必做的30道基础练习题(附保姆级答案解析)

这里是为 Python 真正的小白 准备的 30道超基础练习题(2026年视角),难度从输入输出 → 变量 → 条件 → 循环 → 字符串 → 列表 → 函数逐步递增。 每道题都附带: * 题目描述 * 参考答案(最简单、最清晰的写法) * 核心知识点 + 小提示(保姆级解析) 建议做法: 先自己写 10–15 分钟 → 看不懂再看答案 → 看完答案立刻自己敲一遍 → 改一改输入试试不同情况。 1–10:最基础(输入输出 + 变量 + 运算) 1. 写一个程序,打印 “Hello, Python小白!2026加油!” print("Hello, Python小白!2026加油!") 2. 定义两个变量 a=

By Ne0inhk
Python爬虫(11)Python数据存储实战:深入解析NoSQL数据库的核心应用与实战

Python爬虫(11)Python数据存储实战:深入解析NoSQL数据库的核心应用与实战

目录 * 引言 * 一、背景:为什么选择NoSQL存储爬虫数据? * 1.1 爬虫数据的核心挑战 * 1.2 NoSQL数据库的核心优势 * 二、NoSQL数据库在爬虫中的核心应用 * 2.1 MongoDB:文档型数据库的王者 * 2.2 Redis:内存数据库的极致性能 * 三、NoSQL选型与性能优化策略 * 3.1 数据库选型对比 * 3.2 性能优化实战技巧 * 四、总结与未来趋势 * 4.1 核心总结 * Python爬虫相关文章(推荐) 引言 在Python爬虫开发中,数据存储的效率和扩展性直接决定了项目的长期价值。传统关系型数据库(如MySQL)虽然支持事务和复杂查询,但在应对‌动态数据结构‌、‌海量数据存储‌和‌高并发写入‌时往往捉襟见肘。而‌NoSQL数据库‌

By Ne0inhk
标准 Python 项目结构

标准 Python 项目结构

理解 Python 项目的通用结构对于初学者来说非常重要。虽然每个项目可能略有不同,但大多数规范、可维护的 Python 项目都遵循一些常见的组织模式。 常见的项目结构如下: my_project/ # 项目根目录 ├── my_package/ # 主要 Python 包(模块集合) │ ├── __init__.py # 标识这是一个 Python 包 │ ├── core.py # 核心逻辑 │ ├── utils.py # 工具函数 │ └── ... # 其他模块 ├── tests/ # 单元测试目录 │ ├── __init__.py │ ├── test_core.py │ └── test_utils.py ├── docs/ # 文档(可选) ├── examples/ # 使用示例(可选) ├── requirements.txt # 依赖列表 ├── setup.

By Ne0inhk