Java 函数式编程思考 —— 授人以渔

引言

最近在使用函数式编程时,突然有了一点心得体会,简单说,用好了函数式编程,可以极大的实现方法调用的解耦,业务逻辑高度内聚,同时减少不必要的分支语句(if-else)。

一、函数式编程就是Lambda表达式吗?

Java语言早在 JDK8 就提供了函数式编程的基础。

你可能会问,函数编程不就是lambda表达式吗?

的确,大多数开发可能还停留在 lambda 表达式的使用层面,但请注意,我从标题、文章开篇都在强调“函数式编程”,很明显,我有意区别函数式编程和lambda表达式两者的概念。

Java 8 引入的函数式编程到底是什么?最近我在开发过程中遇到了一个场景,才让我解开了这个困扰我的问题。

二、一个小场景——多路调用

我遇到的场景其实并不复杂,或者说我们每天都在写如此场景,我甚至并不知道这么简单的场景有没有相应的专有名词来表示,就暂将其称为“多路调用场景”。让我们简单模拟一下。

场景描述
A Service 和 B Service 都依赖 CService。
A 和 B都用到了 C的一个方法 doProcess(),但 doProcess() 在处理 A 和 B的请求时,又需要拿到 A 或 B 的业务数据。
该如何实现 doProcess() 方法?

@AllArgsConstructor
class AService {private CService cService;public void processData() {List<Object> all = this.getAll();cService.doProcess(all);}private List<Object> getAll() {return Collections.emptyList();}
}@AllArgsConstructor
class BService {private CService cService;public void processData() {List<Object> data = this.queryBDatas();cService.doProcess(data);}private List<Object> queryBDatas() {return new ArrayList<>();}
}class CService {public void doProcess(List<Object> busiData) {// 执行C自己的处理逻辑...busiData.stream().forEach(d -> {System.out.println(d);});}
}
/*** 测试代码*/
public class Test {public static void main(String[] args) {// 实例化服务对象CService cService = new CService();AService aService = new AService(cService);BService bService = new BService(cService);// A -> C 处理请求aService.processData();// B -> C 处理请求bService.processData();}
}

如上代码所示 A、B 的processData 方法都调用C的doProcess 方法,他们都将 doProcess 所需的数据通过参数传递过去。这种实现方式虽然可以成功的适配不同的调用者,但是数据的生成是在调用 doProcess 前,一旦doProcess执行了一些校验逻辑而无法用到这些已经准备好的数据,就可能白白浪费查询资源。

另一种实现是通过循环依赖,将A或B的实例反过来也注入到 C 服务中,在 doProcess 中,需要用到A 或 B 的数据时才去查询。这种实现方式虽然可以实现懒加载,但又引入了另一个问题,就是高耦合性,而且依然需要通过 if-else 判断具体是要执行 A.getAll 还是需要执行 B.queryList,代码冗杂不说,扩展性也很糟糕。

当然,上述代码只是个模型,实际业务可能比这还要复杂。那到底有没有一种,既可以实现懒加载,又高度内聚,不需要循环依赖的实现方式呢

三、授人以鱼不如授人以渔

请原谅我起了一个这么哗众取宠的小节标题,我后面会解释。

3.1 传统思路的弊端

传统的实现思路,将数据提前准备好传递过去,或使用循环依赖,增加判断条件,执行不同的业务逻辑。

似乎这类实现已被大家习以为常,但就像前面描述的,数据传递可能会造成性能、资源等浪费;传统的延迟处理又需要搭配循环依赖,从而造成严重的依赖混乱问题,明明公共服务被业务服务依赖,公共服务却反过来还要依赖业务服务,扩展性极低。

函数式编程可以很好的解决这个问题!

3.2 授人以渔

Java 8 引入的函数式编程,允许开发者将函数像参数一样传递

直到最近,我才终于理解了这个定义。函数,实际上就是一个具体的处理逻辑,一个解决方案,一个“捕鱼的方法”、一个可开箱即用的“锦囊妙计”。

我们将函数传递到另一个方法中,那么在这个方法中,就可以直接去执行这个函数。

再回到上面的场景中,A和 B调用 C 的 doProcess() 方法,如果使用函数式编程,该如何实现?

首先定义一个业务数据查询函数

// 业务数据查询函数
@FunctionalInterface
public interface BusiDataQueryFunc {List<Object> queryList();
}

然后将其作为参数声明到 doProcess 参数中,令A或B调用时传递一个具体的实现逻辑,如下所示:

@AllArgsConstructor
class AService {private CService cService;public void processData() {cService.doProcess(() -> this.getAll());}private List<Object> getAll() {// A 业务数据查询逻辑return Collections.emptyList();}
}@AllArgsConstructor
class BService {private CService cService;public void processData() {// 传入数据查询函数cService.doProcess(() -> this.queryBDatas());}private List<Object> queryBDatas() {// B 业务数据查询逻辑return new ArrayList<>();}
}class CService {public void doProcess(BusiDataQueryFunc busiDataQueryFunc) {// 查询调用者所需数据List<Object> busiData = busiDataQueryFunc.queryList();// 执行C自己的处理逻辑...busiData.stream().forEach(d -> {System.out.println(d);});}
}

如此,在doProcess中,就不需要使用任何的 if 判断,同时也实现了懒加载获取到业务的数据。相比传统的将数据传递,或通过特定参数加if-else按条件查询,耦合度更低,这是集性能强、扩展性强、耦合度低等优点于一身的优秀实现方式

如果把数据传递到方法中,比作授人以鱼,那么函数传递,就是授人以渔。将“捕鱼的方法”告诉被调用者,这就是为什么我将函数式编程称为——授人以渔的开发思想。

四、总结

在多路调用的场景中,通常会需要在被调用方法中使用到调用者的一些数据,传统的编程方式是直接将数据作为参数传递过去,或者通过一些业务标识用if-else的方式来判断该调用哪个业务方法。

直接传递数据的方式,提前将数据准备好,会有性能问题,可能在被调用方法的校验逻辑执行中断,用不到数据,浪费系统资源;而通过普通的if-else 分支,又需要将调用者注入到被依赖方,虽然实现了懒加载,但本身形成了循环依赖,造成了高耦合,存在潜在的开发成本。

所以,Java 8 提供的函数式编程,将获取数据的方式通过函数传递给被调用者,授人以渔,即满足懒加载,又解耦了依赖关系。这在依赖关系复杂的系统中是一个非常有用的设计思想。
在这里插入图片描述

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

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

相关文章

clickhouse学习之路----clickhouse的特点及安装

clickhouse学习笔记 反正都有学不完的技术&#xff0c;不如就学一学clickhouse吧 文章目录 clickhouse学习笔记clickhouse的特点1.列式存储2. DBMS 的功能3.多样化引擎4.高吞吐写入能力5.数据分区与线程级并行 clickhouse安装1.关闭防火墙2.CentOS 取消打开文件数限制3.安装依…

Python 运行代码

一、Python运行代码 可以使用三种方式运行Python&#xff0c;如下&#xff1a; 1、交互式 通过命令行窗口进入 Python 并开始在交互式解释器中开始编写 Python 代码 2、命令行脚本 可以把代码放到文件中&#xff0c;通过python 文件名.py命令执行代码&#xff0c;如下&#xff…

机器学习——pca降维/交叉验证/网格交叉验证

1、pca降维&#xff1a;目的是提升模型训练速度 定义&#xff1a; 使用方法&#xff1a;给训练数据或者测试数据进行降维处理 给训练数据降维 给测试数据降维&#xff1a;这里1就要用transform&#xff0c;而不是fit_transform&#xff0c;因为之前训练数据降维时特征已经确定…

构建基于neo4j知识图谱、elasticsearch全文检索的数字知识库

前言&#xff1a; 在数字化时代&#xff0c;知识库的建设正逐渐成为企业、学术机构和个人的重要资产。本文将介绍如何使用neo4j和elasticsearch这两种强大的数据库技术来构建知识库&#xff0c;并对其进行比较和探讨。 技术栈&#xff1a; springbootvueneo4jelasticsearch…

【每日一题】1993. 树上的操作

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;深度优先搜索 写在最后 Tag 【深度优先搜索】【树】【设计数据结构】【2023-09-23】 题目来源 1993. 树上的操作 题目解读 本题是一个设计类的题目&#xff0c;对于设计类的题目就一步步的实现题目要求的成员方法即可…

【ACDC数据集】:预处理ACDC心脏3D MRI影像数据集到VOC数据集格式,nii转为jpg,label转为png

【Segment Anything Model】做分割的专栏链接&#xff0c;欢迎来学习。 【博主微信】cvxiaoyixiao 本专栏为公开数据集的预处理&#xff0c;持续更新中。 文章目录 1️⃣ ACDC数据集介绍2️⃣ ACDC数据集样例 3️⃣ 预处理ACDC目标 4️⃣ 处理结果样图 5️⃣ 代码 6️⃣ 划分测…

Maven高级---分模块设计,继承(继承关系/版本锁定/自定义属性)

目录 分模块设计 继承与聚合 继承关系 ​案例​ 版本锁定 自定义属性/引用属性 分模块设计 把一个项目拆分成不同的模块 我们可以把原来一个项目包中的东西单独提出来作为一个模块,也是解耦的思想 然后我们可以通过引入依赖的方式将这两个模块引入,如下 继承与聚合 继…

xxe攻击(XML外部实体)

1.定义 XML用于标记电子文件使其具有结构性的标记语言&#xff0c;可以用来标记数据、定义数据类型&#xff0c;是一种允许用户对自己的标记语言进行定义的源语言。XML文档结构包括XML声明、DTD文档类型定义&#xff08;可选&#xff09;、文档元素。 http://www.w3school.com.…

初识操作系统

目录 一.操作系统的概念 二.正确理解“管理” 三.操作系统对硬件进行管理 四.系统调用接口 五.用户操作接口 一.操作系统的概念 操作系统是一款进行软硬件资源管理的软件 现在我们知道了操作系统的概念了&#xff0c;那么为什么要有操作系统呢&#xff1f; 操作系统将软…

李宏毅hw-9:Explainable ML

——欲速则不达&#xff0c;我已经很幸运了&#xff0c;只要珍惜这份幸运就好了&#xff0c;不必患得患失&#xff0c;慢慢来。 ----查漏补缺&#xff1a; 1.关于这个os.listdir的使用 2.从‘num_文件名.jpg’中提取出数值&#xff1a; 3.slic图像分割标记函数的作用&#xf…

光电探测器怎么选

光电探测器&#xff0c;也称为PD&#xff08;photodetector&#xff09; 基本原理是&#xff0c;光信号进入光电探测器转换为电压信号&#xff0c;这个电压信号会很弱&#xff0c;微伏或者毫伏级别。 所以PD分为带放大和不带放大 带放大呢&#xff0c;是因为信号太弱&#xf…

Vue系列(二)之 基础语法上篇【插值,指令,过滤器,计算属性监听属性】以及购物车实现

目录 一. 插值 1.1 文本 1.2 原始HTML 1.3 属性 1.4 表达式 二. 指令 2.1 v-if/v-else-if/v-else指令 2.2 v-show指令 2.3 v-for指令 2.4 下拉框/复选框 2.5 动态参数 三. 过滤器 3.1 局部过滤器基本应用 3.2 局部过滤器串行使用 3.3 局部过滤器传参 3.4 全局过…

QT学习之创建项目

1、添加快捷键到桌面 找到所在安装路径Qt5.14.1\Tools\QtCreator\bin添加至桌面 双击后打开 2、创建项目 点击下一步 再点击下一步 下一步 当前暂时选择32位&#xff0c;之后点击完成 3、进入编辑页面并运行 运行快捷键&#xff1a;CtrlR 运行界面点击按钮&#xff1a; 运行…

单臂路由的详细配置步骤

1、单臂路由概述 单臂路由&#xff08;router-on-a-stick&#xff09;是指在路由器的一个接口上通过配置子接口&#xff08;或“逻辑接口”&#xff0c;并不存在真正物理接口&#xff09;的方式&#xff0c;实现原来相互隔离的不同VLAN&#xff08;虚拟局域网&#xff09;之间…

C++笔记之引用折叠规则

C笔记之引用折叠规则 文章目录 C笔记之引用折叠规则1. 当两个左值引用结合在一起时&#xff0c;它们会折叠成一个左值引用。2. 当一个左值引用和一个右值引用结合在一起时&#xff0c;它们会折叠成一个左值引用。3. 当两个右值引用结合在一起时&#xff0c;它们也会折叠成一个右…

【网络协议】Http-上

Http请求结构&#xff1a; 结构图1&#xff1a; 实验解析请求报文&#xff1a; 1.在Edge浏览器上输入ip地址端口号文件资源&#xff0c;也就是下图中的120.XX.139.29:8888/A/B/c.html 2.我的程序接收到了一个没有有效载荷的http请求(呼应上面的结构图1)&#xff0c;如下 GET …

Python机器学习实战-特征重要性分析方法(1):排列重要性(附源码和实现效果)

实现功能 排列重要性 PermutationImportance&#xff1a;该方法会随机排列每个特征的值&#xff0c;然后监控模型性能下降的程度。如果获得了更大的下降意味着特征更重要 实现代码 from sklearn.datasets import load_breast_cancer from sklearn.ensemble import RandomFore…

C语言之字符函数字符串函数篇(2)

目录 字符串查找 strstr strstr的使用 strstr的模拟实现 分析 考虑点 代码 strt strtok的使用 循环改进 错误信息报告 strerror 错误码的错误信息 strerror的使用 perror 字符操作 字符分类函数 字符转化函数 今天我们接着讲字符串函数&#xff0c;也…

balenaEtcher格式化的U盘恢复原来样子

今天用balenaEtcher烧录了U盘&#xff0c;刷机N1盒子openwrt, 刷完机结果发现自己的U盘电脑最后只有167M 想要恢复原来的64G以下是方法&#xff0c;使用win10系统 1、打开命令行cmd&#xff08;winr打开运行&#xff0c;在其中输入cmd就可以打开&#xff09;&#xff1b; 2…