当前位置: 首页 > article >正文

使用 Function 来编写策略模式:优雅而高效的设计模式实践

引言:为什么选择策略模式?

策略模式(Strategy Pattern)是行为设计模式中的经典之一,它允许我们定义一系列的算法或操作,并使得它们可以互换使用。策略模式的关键思想是将算法的实现与使用它们的上下文分离,使得同一操作可以根据不同的策略来实现。

但在 Java 中,如何实现这个设计模式呢?通常我们会使用接口、抽象类和具体实现来完成,但这往往导致代码复杂、冗长。那么,如何利用 Java 8 引入的 Function 来使策略模式变得更加简洁和优雅呢?

这篇文章将展示如何通过 Function 来实现策略模式,从而使得策略模式更加灵活、简洁且易于维护。


一、策略模式的传统实现

首先,让我们看看传统的策略模式是如何实现的。在这个例子中,我们有一个 PaymentStrategy,它有多种不同的支付方式,例如 信用卡支付PayPal 支付

1. 定义策略接口:
interface PaymentStrategy {void pay(double amount);
}
2. 实现具体策略:
class CreditCardPayment implements PaymentStrategy {private String cardNumber;public CreditCardPayment(String cardNumber) {this.cardNumber = cardNumber;}@Overridepublic void pay(double amount) {System.out.println("Paid " + amount + " using Credit Card: " + cardNumber);}
}class PayPalPayment implements PaymentStrategy {private String email;public PayPalPayment(String email) {this.email = email;}@Overridepublic void pay(double amount) {System.out.println("Paid " + amount + " using PayPal account: " + email);}
}
3. 策略上下文:
class PaymentContext {private PaymentStrategy strategy;public PaymentContext(PaymentStrategy strategy) {this.strategy = strategy;}public void executePayment(double amount) {strategy.pay(amount);}
}
4. 客户端代码:
public class Main {public static void main(String[] args) {PaymentStrategy creditCardPayment = new CreditCardPayment("1234-5678-9876");PaymentStrategy payPalPayment = new PayPalPayment("user@example.com");PaymentContext context = new PaymentContext(creditCardPayment);context.executePayment(100.00);context = new PaymentContext(payPalPayment);context.executePayment(200.00);}
}

通过这种方式,我们使用不同的策略来支付不同的金额。代码看起来清晰,但我们有很多重复的代码结构,如 PaymentStrategy 接口和各个策略类的实现。接下来,我们将使用 Function 来改进这一设计。


二、使用 Function 改进策略模式

在 Java 8 引入的 Function 可以帮助我们简化这个过程。Function 是一个函数式接口,它接受一个输入并返回一个结果。在策略模式中,我们将 Function 作为策略的实现来代替原本的类结构。

1. 使用 Function 定义策略:

我们不再需要定义一个接口或多个类来实现不同的策略,而是直接使用 Function 来表示每种策略。每个 Function 接收一个 double 类型的支付金额并执行支付操作。

2. 改进的策略代码:
import java.util.function.Function;public class PaymentStrategy {// 使用 Function 来表示支付策略public static Function<Double, Void> creditCardPayment(String cardNumber) {return amount -> {System.out.println("Paid " + amount + " using Credit Card: " + cardNumber);return null; // 返回类型为 Void,表示没有返回值};}public static Function<Double, Void> payPalPayment(String email) {return amount -> {System.out.println("Paid " + amount + " using PayPal account: " + email);return null;};}
}
3. 策略上下文(更简化):
class PaymentContext {private Function<Double, Void> strategy;public PaymentContext(Function<Double, Void> strategy) {this.strategy = strategy;}public void executePayment(double amount) {strategy.apply(amount);  // 使用 Function 的 apply 方法执行支付}
}
4. 客户端代码:
public class Main {public static void main(String[] args) {// 使用 Function 传递支付策略Function<Double, Void> creditCardPayment = PaymentStrategy.creditCardPayment("1234-5678-9876");Function<Double, Void> payPalPayment = PaymentStrategy.payPalPayment("user@example.com");PaymentContext context = new PaymentContext(creditCardPayment);context.executePayment(100.00);context = new PaymentContext(payPalPayment);context.executePayment(200.00);}
}

三、优势分析:

  1. 简洁的代码:

    • 使用 Function 来代替传统的接口和具体类,不仅减少了类的数量,而且让代码更加简洁。我们不需要为每个策略创建一个类,所有策略的实现都可以在一个地方集中定义。
  2. 灵活性:

    • Function 可以非常容易地通过 Lambda 表达式来定义,也可以根据需求动态调整策略。而且,Function 是一个高度可组合的接口,可以通过链式调用来组合多个函数。
  3. 代码维护:

    • 使用 Function 来表达策略使得每个策略变得更加简洁且独立,开发者可以轻松地替换策略或修改策略的行为,而无需修改复杂的类结构。
  4. 与 Java 8+ 特性结合:

    • 结合 Java 8 的 Lambda 表达式、Stream API 等特性,Function 让代码更加符合现代 Java 编程风格。

四、进一步优化:策略的复用和组合

一个重要的应用场景是我们可以通过组合多个 Function 来复用现有策略或创建新策略。例如,我们可以组合支付策略和折扣策略来构建更复杂的支付流程。

示例:组合策略

假设我们需要为支付金额应用折扣:

public class DiscountedPayment {public static Function<Double, Double> applyDiscount(double discountRate) {return amount -> amount - (amount * discountRate);}public static Function<Double, Void> creditCardPaymentWithDiscount(String cardNumber, double discountRate) {Function<Double, Double> discount = applyDiscount(discountRate);return amount -> {double discountedAmount = discount.apply(amount);System.out.println("Paid " + discountedAmount + " using Credit Card: " + cardNumber);return null;};}
}

我们可以将折扣策略与支付策略组合:

Function<Double, Void> creditCardPaymentWithDiscount = DiscountedPayment.creditCardPaymentWithDiscount("1234-5678-9876", 0.1);
PaymentContext context = new PaymentContext(creditCardPaymentWithDiscount);
context.executePayment(100.00);  // 输出:Paid 90.0 using Credit Card: 1234-5678-9876

五、总结:优雅而高效的策略模式

通过 Function,我们不仅让策略模式更加简洁,而且增强了代码的灵活性和可维护性。借助 Lambda 表达式,Java 8+ 中的函数式编程特性,我们能够以一种更现代、更优雅的方式实现策略模式。

本篇要点回顾:
  1. 使用 Function 替代传统的策略接口与具体实现类,简化了策略模式的实现。
  2. Function 的高度灵活性和组合能力使得策略模式更加可扩展。
  3. 结合 Java 8+ 的特性,策略模式变得更加优雅、高效,减少了代码冗余。

你可以尝试在自己的项目中应用这个技巧,提升代码的简洁性和可维护性。

推荐阅读文章

  • 由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)

  • 如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系

  • HTTP、HTTPS、Cookie 和 Session 之间的关系

  • 什么是 Cookie?简单介绍与使用方法

  • 什么是 Session?如何应用?

  • 使用 Spring 框架构建 MVC 应用程序:初学者教程

  • 有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误

  • 如何理解应用 Java 多线程与并发编程?

  • 把握Java泛型的艺术:协变、逆变与不可变性一网打尽

  • Java Spring 中常用的 @PostConstruct 注解使用总结

  • 如何理解线程安全这个概念?

  • 理解 Java 桥接方法

  • Spring 整合嵌入式 Tomcat 容器

  • Tomcat 如何加载 SpringMVC 组件

  • “在什么情况下类需要实现 Serializable,什么情况下又不需要(一)?”

  • “避免序列化灾难:掌握实现 Serializable 的真相!(二)”

  • 如何自定义一个自己的 Spring Boot Starter 组件(从入门到实践)

  • 解密 Redis:如何通过 IO 多路复用征服高并发挑战!

  • 线程 vs 虚拟线程:深入理解及区别

  • 深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别

  • 10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!

  • “打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”

  • Java 中消除 If-else 技巧总结

  • 线程池的核心参数配置(仅供参考)

  • 【人工智能】聊聊Transformer,深度学习的一股清流(13)

  • Java 枚举的几个常用技巧,你可以试着用用

  • 由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)

  • 如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系

  • HTTP、HTTPS、Cookie 和 Session 之间的关系

  • 使用 Spring 框架构建 MVC 应用程序:初学者教程

  • 有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误

  • Java Spring 中常用的 @PostConstruct 注解使用总结

  • 线程 vs 虚拟线程:深入理解及区别

  • 深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别

  • 10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!

  • 探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)

  • 为什么用了 @Builder 反而报错?深入理解 Lombok 的“暗坑”与解决方案(二)

相关文章:

使用 Function 来编写策略模式:优雅而高效的设计模式实践

引言&#xff1a;为什么选择策略模式&#xff1f; 策略模式&#xff08;Strategy Pattern&#xff09;是行为设计模式中的经典之一&#xff0c;它允许我们定义一系列的算法或操作&#xff0c;并使得它们可以互换使用。策略模式的关键思想是将算法的实现与使用它们的上下文分离…...

Java字符串处理

Java字符串处理全解析&#xff1a;String、StringBuilder与StringBuffer 一、String类基础 1. String的本质 不可变对象&#xff1a;Java中的String对象一旦创建就不能修改底层实现&#xff1a;基于private final char value[]字符数组字符串池&#xff1a;JVM维护的特殊存储…...

JS实现RSA加密

目录 目标 环境 实现RSA加解密 计算RSA加密允许的最大字节长度 目标 使用JS实现RSA加密解密。计算RSA加密允许的最大字节长度。 环境 node-rsa 实现RSA加解密 const NodeRSA require(node-rsa);function getKey() {const keyLength512// 创建 RSA 密钥对const key new …...

MySQL GTID集合运算函数总结

MySQL GTID 有一些运算函数可以帮助我们在运维工作中提高运维效率。 1 GTID内置函数 MySQL 包含GTID_SUBSET、GTID_SUBTRACT、WAIT_FOR_EXECUTED_GTID_SET、WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS 4个内置函数&#xff0c;用于GTID集合的基本运算。 1.1 GTID_SUBSET(set1,set2) …...

从“链主”到“全链”:供应链数字化转型的底层逻辑

1. 制造业与供应链数字化转型的必然性 1.1. 核心概念与战略重要性 制造业的数字化转型&#xff0c;是利用新一代数字技术&#xff08;如工业互联网、人工智能、大数据、云计算、边缘计算等&#xff09;对制造业的整体价值链进行根本性重塑的过程。这不仅涉及技术的应用&#…...

学习笔记十五——rust柯里化,看不懂 `fn add(x) -> impl Fn(y)` 的同学点进来!

&#x1f9e0; Rust 柯里化从零讲透&#xff1a;看不懂 fn add(x) -> impl Fn(y) 的同学点进来&#xff01; &#x1f354; 一、什么是柯里化&#xff1f;先用一个超好懂的生活比喻 假设你在点一个汉堡&#xff1a; 你说&#xff1a;我要点一个鸡腿汉堡&#xff01; 店员…...

定制化突围:遨游防爆手机的差异化竞争策略

在石油、化工、矿山等危险作业场景中&#xff0c;随着工业智能化与安全生产需求的升级&#xff0c;行业竞争逐渐从单一产品性能的比拼转向场景化解决方案的深度较量。遨游通讯以九重防爆标准为技术底座&#xff0c;融合多模稳控系统与全景前瞻架构&#xff0c;开辟出"千行…...

【Java学习笔记】进制与进制转换

进制与进制转换 一、进制介绍 二进制&#xff1a;0、1&#xff0c;满 2 进 1&#xff0c;以 0b 或 0B 开头。 十进制&#xff1a;0-9&#xff0c;满 10 进 1。 八进制&#xff1a;0-7&#xff0c;满 8 进 1&#xff0c;以数字 0 开头表示。 十六进制&#xff1a;0-9 及 A(10…...

士兵乱斗(贪心)

问题 B: 士兵乱斗 - USCOJ...

【C++面向对象】封装(下):探索C++运算符重载设计精髓

&#x1f525;个人主页 &#x1f525; &#x1f608;所属专栏&#x1f608; 每文一诗 &#x1f4aa;&#x1f3fc; 年年岁岁花相似&#xff0c;岁岁年年人不同 —— 唐/刘希夷《代悲白头翁》 译文&#xff1a;年年岁岁繁花依旧&#xff0c;岁岁年年看花之人却不相同 目录 C运…...

JVM初探——走进类加载机制|三大特性 | 打破双亲委派SPI机制详解

目录 JVM是什么&#xff1f; 类加载机制 Class装载到JVM的过程 装载&#xff08;load&#xff09;——查找和导入class文件 链接&#xff08;link&#xff09;——验证、准备、解析 验证&#xff08;verify&#xff09;——保证加载类的正确性 准备&#xff08;Prepare&…...

[图论]Kruskal

Kruskal 本质&#xff1a;贪心&#xff0c;对边进行操作。存储结构&#xff1a;边集数组。适用对象&#xff1a;可为负权图&#xff0c;可求最大生成树。核心思想&#xff1a;最短的边一定在最小生成树(MST)上&#xff0c;对最短的边进行贪心。算法流程&#xff1a;对全体边集…...

UML-饮料自助销售系统(无法找零)序列图

一、题目&#xff1a; 在饮料自动销售系统中&#xff0c;顾客选择想要的饮料。系统提示需要投入的金额&#xff0c;顾客从机器的前端钱币口投入钱币&#xff0c;钱币到达钱币记录仪&#xff0c;记录仪更新自己的选择。正常时记录仪通知分配器分发饮料到机器前端&#xff0c;但可…...

Nginx Http配置整理

一、nginx 配置参数&#xff1a; server {#SSL 默认访问端口号为 443listen 443 ssl;#请填写绑定证书的域名server_name cloud.tencent.com; #请填写证书文件的相对路径或绝对路径ssl_certificate cloud.tencent.com_bundle.crt; #请填写私钥文件的相对路径或绝对路径ssl_cer…...

爬虫利器SpiderTools谷歌插件教程v1.0.0!!!web端JavaScript环境检测!!!

SpiderTools谷歌插件教程v1.0.0 一、SpiderTools简介二、下载通道三、插件介绍四、插件使用五、工具函数使用 一、SpiderTools简介 SpiderTools主要用于检测和监控网页的JavaScript运行环境。该插件可以帮助开发者更好地查看网页运行环境&#xff0c;特别是在处理复杂的前端环…...

计算机视觉算法实战——基于YOLOv8的农田智能虫情测报灯害虫种类识别系统开发指南

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​​​ ​​​​​​​​​ ​​ 一、智能虫情监测领域概述 1.1 农业虫害防治现状 全球每年因虫害造成的粮食损失达20%-40%&#xff0c;我…...

14-算法打卡-哈希表-基本概念-第十四天

1 基本概念 1.1 哈希表 百度百科解释&#xff1a; 散列表&#xff08;Hash table&#xff0c;也叫哈希表&#xff09;&#xff0c;是根据关键码值(Key value)而直接进行访问的数据结构。也就是说&#xff0c;它通过把关键码值映射到表中一个位置来访问记录&#xff0c;以加快…...

趣味编程之分布式系统:负载均衡的“雨露均沾“艺术

#此篇文章由Deepseek大力支持&#x1f60b; 凌晨三点&#xff0c;西二旗某火锅店后厨—— “羊肉卷走3号桌&#xff01;” “肥牛卷去7号&#xff01;” “虾滑优先给VIP区&#xff01;” 我蹲在传菜口的监控屏幕前&#xff0c;看着机器人服务生们忙而不乱地穿梭。突然间&am…...

第十六届蓝桥杯大赛软件赛省赛 C++ 大学 B 组 部分题解

赛时参加的是Python组&#xff0c;这是赛后写的题解&#xff0c;还有两题暂时还不会&#xff0c;待更新 题目链接题目列表 - 洛谷 | 计算机科学教育新生态 A 移动距离 答案&#xff1a;1576 C 可分解的正整数 Python3 import itertools from functools import cmp_to_ke…...

考研数据结构之顺序查找、折半查找与分块查找详解(包含真题及解析)

考研数据结构之顺序查找、折半查找与分块查找详解 一、顺序查找&#xff08;Sequential Search&#xff09; 1.1 基本思想 顺序查找是最基础的查找算法&#xff0c;通过遍历数据集合逐个比较目标值与当前元素&#xff0c;直到找到匹配项或遍历结束。其核心特点是&#xff1a…...

英文查重的时候参考文献显示重复是怎么回事?

像上图这样参考文献部分有颜色的情况&#xff0c;是属于参考文献没有排除干净的问题。 如何解决这样的问题&#xff1f; 首先第一步&#xff0c;先确认该报告是不是排除参考文献的版本&#xff1b; 第二步&#xff0c;如果是排除参考文献的版本&#xff0c;且参考文献仍然有…...

八股文---MySQl(3)

目录 12.事务的特性是什么&#xff1f;可以详细说一下吗&#xff1f; 回答 13并发事务带来哪些问题&#xff1f;怎么解决这些问题呢&#xff1f;MySQL的默认隔离级别是&#xff1f; 脏读&#xff1a;一个事务读到另外一个事务还没有提交的数据。 不可重复读&#xff1a;一个…...

基于labview的钢琴程序设计

部分程序如下 按照上图子vi更改输出频率即可 若需完整程序可以联系我...

国内网络设备厂商名单(List of Domestic Network Equipment Manufacturers)

国内网络设备厂商名单 运维工程师必须广泛熟悉国内外各大厂商的设备&#xff0c;深入掌握其应用场景、功能特点及优势。这不仅有助于在故障排查时迅速定位问题&#xff0c;还能在系统设计、优化与升级中做出更合理的决策。对设备特性的精准把握&#xff0c;能够显著提升运维效…...

基于CNN+ViT的蔬果图像分类实验

本文只是做一个简单融合的实验&#xff0c;没有任何新颖&#xff0c;大家看看就行了。 1.数据集 本文所采用的数据集为Fruit-360 果蔬图像数据集&#xff0c;该数据集由 Horea Mureșan 等人整理并发布于 GitHub&#xff08;项目地址&#xff1a;Horea94/Fruit-Images-Datase…...

高级语言调用C接口(五)结构体(3)-arkts

上一篇文章提到了&#xff0c;arkts和C接口之前还有一个Napi层&#xff0c;这层代码最大的优势就是C/C编码&#xff0c;这样&#xff0c;我们只需要把数据通过Json格式传递到Napi层&#xff0c;Napi层再定义一个结构体并赋值即可。arkts层是TypeScript代码&#xff0c;想定义成…...

【虚幻C++笔记】接口

目录 概述创建接口 概述 简单的说&#xff0c;接口提供一组公共的方法&#xff0c;不同的对象中继承这些方法后可以有不同的具体实现。任何使用接口的类都必须实现这些接口。实现解耦解决多继承的问题 创建接口 // Fill out your copyright notice in the Description page o…...

【MCP】第一篇:MCP协议深度解析——大模型时代的“神经连接层“架构揭秘

【MCP】第一篇&#xff1a;MCP协议深度解析——大模型时代的"神经连接层"架构揭秘 一、什么是MCP&#xff1f;二、为什么需要MCP&#xff1f;三、MCP的架构四、MCP与AI交互的原理4.1 ReAct&#xff08;Reasoning Acting&#xff09;模式4.2 Function Calling 模式 五…...

实时模式下 libaom 与 x264 编码对比实验

前沿 理论基础&#xff1a;在相同视频质量下&#xff0c;AV1的压缩率比H.264高约30%-50%。实时模式&#xff1a;视频编码中的实时模式&#xff0c;其核心目标是平衡编码效率与延迟要求&#xff0c;尤其在视频会议、直播、实时通信等场景中至关重要。 低延迟要求&#xff1a;编…...

学习海康VisionMaster之矩形检测

这几天太忙了&#xff0c;好几天没有学习了&#xff0c;今天终于空下来了&#xff0c;继续学习之路吧。 一&#xff1a;进一步学习了 今天学习下VisionMaster中的矩形检测&#xff0c;这个一开始我以为是形态学方面的检测&#xff0c;实际操作下来其实还是边缘直线的衍生应用&…...