基于lambda简化设计模式
写在文章开头
本文将演示基于函数式编程的理念,优化设计模式中繁琐的模板化编码开发,以保证用尽可能少的代码做尽可能多的事,希望对你有帮助。

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili 。
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

详解lambda重构设计模式思路
策略模式
不同的业务对于字符串有着不同的匹配规则,例如:长度是否大于10、是否全是大小、是否是电话号码等,为了保证规则的复用,我们通常会采用策略模式封装这些规则:

对应的我们给出策略封装的接口定义:
/*** 定义策略模式的接口*/
public interface ValidationStrategy {/*** 校验该字符串是否符合要求,若符合则返回true* @param str* @return*/boolean execute(String str);
}
后续让我们的规则校验器将这个抽象的校验规则接口聚合进来,如此我们就可以按照需求传入对应的校验规则策略strategy:
public class Validator {//将校验规则聚合进来private ValidationStrategy strategy;public Validator() {}public Validator(ValidationStrategy strategy) {this.strategy = strategy;}//基于聚合的strategy校验字符串strpublic boolean validate(String str) {return strategy.execute(str);}
}
以下便是判断字符串是否全小写的校验规则实现:
/*** 判断是否全为小写*/
public class IsAllLowerCase implements ValidationStrategy {@Overridepublic boolean execute(String str) {return str.matches("[a-z]+");}
}
完成后我们就可以将IsAllLowerCase实例化并传入校验器Validator 中进行使用了:
//校验是否全是小写
Validator validator = new Validator(new IsAllLowerCase());System.out.println(validator.validate("abc"));//true
可以看到我们的校验规则基本上一行就可以搞定,为了这仅有的一行逻辑实现而编写大量的前置模板实在繁琐,对此我们不妨基于之前所介绍的函数式编程的思路分析以下,我们的ValidationStrategy本质上就只有一个抽象接口,所以它完全可以被认定为一个函数式接口,所以我们不妨将FunctionalInterface这个注解加上:
@FunctionalInterface
public interface ValidationStrategy {/*** 校验该字符串是否符合要求,若符合则返回true* @param str* @return*/boolean execute(String str);
}
参考其抽象方法,我们可以看到入参为String,返回值为boolean,由此推出函数描述符为(String)->boolean,我们以上文全小写规则为例,按照这个函数描述符的规则,我们的表达式就是:(s)->s.matches("[a-z]+"),是不是简化了许多呢?

对应的我们给出简化后的代码:
//校验是否全是小写Validator validator = new Validator((s)->s.matches("[a-z]+"));System.out.println(validator.validate("abc"));
模板方法
模板方法的理念是对于固化模板自实现,然后将个性化业务下沉到不同业务场景自行开发,我们以银行找到会员为例,其大体步骤为:
- 查询会员信息(固定)。
- 招待会员。(不同银行流程不同)。

基于这个需求我们得出这样一个抽象类:
public abstract class Banking {public void processCustomer(int id) {//查询会员信息String customer = getCustomerWithId(id);//招待会员makeCustomerHappy(customer);}private String getCustomerWithId(int id) {return "user-"+id;}//不同银行有不同的实现protected abstract void makeCustomerHappy(String customer);
}
对应的我们给出BankingA 的招待逻辑:
public class BankingA extends Banking {@Overrideprotected void makeCustomerHappy(String customer) {System.out.println("请"+customer+"吃饭,并为其办理业务");}
}
对应我们给出使用代码和输出结果:
Banking bankingA = new BankingA();bankingA.processCustomer(1);//输出请user-1吃饭,并为其办理业务
还是一样的问题,为了仅仅一段简单的输出去适配模板方法从而编写出一段繁琐的代码,很明显是需求简化的。
分析抽象方法makeCustomerHappy的定义,要求传入String返回void,由此我们按照函数式编程的套路得出可能要用到的函数式接口的函数描述符为:(String)->Void,于是我们得到了一个JDK8自带的接口Consumer,它的函数签名为(T)->Void,注意这个T是泛型,所以我们完全可以将String类等价代入:

这里我们也给出Consumer的源码,读者可自行查阅:
@FunctionalInterface
public interface Consumer<T> {//......void accept(T t);}
对此我们将抽象类Banking 加以改造,将抽象方法makeCustomerHappy改为Consumer接口:
public class Banking {public void processCustomer(int id, Consumer<String> makeCustomerHappy) {//查询会员信息String customer = getCustomerWithId(id);//招待会员makeCustomerHappy.accept(customer);}private String getCustomerWithId(int id) {return "user-"+id;}}
于是我们的代码就简化成了下面这样:
Banking bankingA = new BankingA();bankingA.processCustomer(1, (s) -> System.out.println("请" + s + "吃饭,并为其办理业务"));
观察者模式
观察者模式算是最经典也最好理解的设计模式,观察者只需将自己注册到感兴趣的主题上,一旦有主题更新就会及时通知观察者处理该消息:

基于这种设计模式的理念,我们给出观察者的接口的定义:
/*** 观察者*/
public interface Observer {//主题有更新调用该方法通知观察者void inform(String msg);
}
然后就是主题接口的定义:
public interface Subject {//注册观察者void registerObserver(Observer observer);//通知消息给所有观察者void notifyObserver();
}
对此我们给出一个观察者的实现:
public class Observer1 implements Observer {@Overridepublic void inform(String msg) {System.out.println("观察者1收到通知,内容为:" + msg);}
}
最后就是主题类的实现,我们将观察者聚合,如果观察者对SubJect1 感兴趣,则通过registerObserver完成注册,一旦主题要发布新消息就可以通过notifyObserver及时通知每一个订阅者:
public class SubJect1 implements Subject {private String msg;public SubJect1(String msg) {this.msg = msg;}private List<Observer> observerList = new ArrayList<>();//将观察者添加到订阅者列表@Overridepublic void registerObserver(Observer observer) {observerList.add(observer);}//有新消息通知订阅列表的所有用户@Overridepublic void notifyObserver() {observerList.forEach(o -> o.inform(msg));}
测试代码和对应输出结果如下所示:
public static void main(String[] args) {SubJect1 subJect1 = new SubJect1("请大家学习《基于lambda简化设计模式》");//注册订阅者subJect1.registerObserver(new Observer1());//主题发起通知subJect1.notifyObserver();//观察者1收到通知,内容为:请大家学习《基于lambda简化设计模式》}
每一个观察都是一行代码,为了这一行创建一个类显得有些繁琐,仔细查看registerObserver方法的定义我们可以得出该方法的签名为Observer->Void,由此我们可以推导出对应的lambda表达式:

由此我们可以直接将上述Observer1的代码简化为lambda表达式,对应代码示例如下:
subJect1.registerObserver(s -> System.out.println("观察者1收到消息" + s));
责任链模式
责任链在Spring或者Netty这种大型框架中非常常见,它通过链式关系顺序的调用处理器处理当前业务数据,举个例子,我们希望字符串被对象1处理完成之后要转交给对象2处理,并且我们后续可能还会交给更多的对象处理,通过对需求的梳理和抽象,这个功能可以通过责任链模式来实现。

首先声明责任链上每一个处理器的抽象类,考虑到通用性笔者将这个类的入参设置为泛型,并且公共方法handle的步骤为:
- 调用自己的
handWork处理输入数据,handWork交给实现类自行编写。 - 若
successor不为空,则将处理结果交给下一个处理器处理,由此构成一条处理链。
public abstract class ProcessingObject<T> {/*** 下一个处理器*/private ProcessingObject<T> successor;public ProcessingObject<T> getSuccessor() {return successor;}public void setSuccessor(ProcessingObject<T> successor) {this.successor = successor;}public T handle(T input) {//先自己处理完,如果有后继责任链,则交给后面的责任链处理,递归下去T t = handWork(input);if (successor != null) {return successor.handWork(t);}return t;}/*** 自己的处理逻辑** @param intput* @return*/abstract T handWork(T intput);
}
对应的我们基于这个抽象类实现两个字符处理器,ProcessingStr1会将收到的中文逗号换位英文逗号:
public class ProcessingStr1 extends ProcessingObject<String> {@OverrideString handWork(String intput) {return intput.replace(",", ",");}
}
而ProcessingStr2 会将中文句号替换为英文句号:
public class ProcessingStr2 extends ProcessingObject<String> {@OverrideString handWork(String intput) {return intput.replace("。", ".");}
}
测试代码和输出结果如下:
public static void main(String[] args) {ProcessingObject<String> p1 = new ProcessingStr1();ProcessingObject<String> p2 = new ProcessingStr2();p1.setSuccessor(p2);System.out.println(p1.handle("hello,world。"));}
可以看到所有的中文符号都被替换成英文符号了:
hello,world.
话不多说,不难看出上文这种传入String返回String的方法,我们完全可以使用UnaryOperator函数式接口,它的函数式签名为(T)->T,很明显将我们的String类代入是成立的:

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {static <T> UnaryOperator<T> identity() {return t -> t;}
}
于是我们的责任链的处理器就被简化为下面这样:
//逗号替换UnaryOperator<String> p1 = i -> i.replace(",", ",");//句号替换
UnaryOperator<String> p2 = i -> i.replace("。", ".");
为了让两个处理器构成逻辑链,我们直接使用函数复合方法andThen,确保p1执行玩调用p2:
Function<String, String> function = p1.andThen(p2);
System.out.println(function.apply("hello,world。"));//hello,world.
小结
对于逻辑比较简单且需要适配设计模式的功能,可以尝试找到合适的函数式接口,按照它的签名实现功能,避免没必要的模板配置工作。
以上便是笔者关于Java8中函数式设计模式的案例实践全部内容,希望对你有帮助。
我是 sharkchili ,CSDN Java 领域博客专家,开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

参考
Java 8 in Action:https://book.douban.com/subject/25912747/
相关文章:
基于lambda简化设计模式
写在文章开头 本文将演示基于函数式编程的理念,优化设计模式中繁琐的模板化编码开发,以保证用尽可能少的代码做尽可能多的事,希望对你有帮助。 Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ÿ…...
揭秘! 经纬恒润“车路云一体化”方案研发服务背后的科技驱动力
随着高级别智能驾驶技术的飞速发展,自动驾驶与路侧基础设施协同合作已成为行业内的又一热点。我国率先提出以“车路云一体化”为核心的战略布局,国家政策密集出台,地方试点积极推进,行业标准日趋完善,智能网联汽车“车…...
Redis操作--RedisTemplate(二)StringRedisTemplate
一、介绍 1、简介 由于存储在 Redis 中的 key 和 value 通常是很常见的 String 类型,Redis模块提供了 RedisConnection 和 RedisTemplate 的扩展,分是 StringRedisConnection 和 StringRedisTemplate,作为字符串操作的解决方案。 通过源码…...
【自动驾驶】ROS中自定义格式的服务通信,含命令行动态传参(c++)
目录 通信流程创建服务器端及客户端新建服务通讯文件修改service的xml及cmakelistCMakeLists.txt编辑 msg 相关配置编译消息相关头文件在cmakelist中包含头文件的路径在service包下编写service.cpp在client包下编写client.cpp测试运行查询服务的相关指令列出目前的所有服务&…...
优思学院|PDCA和DMAIC之间如何选择?
在现代组织中,提升方法、质量和效率是企业追求卓越、保持竞争力的核心目标。在这条道路上,DMAIC(定义、测量、分析、改进、控制)和PDCA(计划、执行、检查、行动)被广泛应用于持续改进和问题解决。这两者虽然…...
5 款最佳 Micro SD 卡恢复软件,助您恢复文件
您是否对数据恢复存在某些疑问,并想知道如何恢复 Micro SD 卡上的文件?如果是,那么在本文中您将找到答案。网上有许多专门用于从 Micro SD 卡或格式化的 Micro 卡恢复已删除文件而设计的软件。因此,在本文中,我们将向您…...
【使用教程】CiA402中的“原点回归模式”和“轮廓位置模式”搭配使用操作实例
使用“原点回归模式”配合“轮廓位置模式”是步进或伺服电机使用过程中最常用的方法,其对于提高自动化生产线的准确性和效率具有重要意义,本文将对正常使用控制电机中发送的命令及顺序进行简要说明。 说明:“原点回归”以“堵转回原点”的方式…...
服务器网络不通排查方案
服务器网络不通排查方案 最近遇到了服务器上服务已经启动,但是在浏览器上无法访问的问题,记录一下排查流程 文章目录 服务器网络不通排查方案netstart排查网络连接信息netstat 命令netstat -aptn 命令 iptables总结 netstart排查网络连接信息 netstat …...
Spring Boot + Vue 跨域配置(CORS)问题解决历程
在使用 Spring Boot 和 Vue 开发前后端分离的项目时,跨域资源共享(CORS)问题是一个常见的挑战。接下来,我将分享我是如何一步步解决这个问题的,包括中间的一些试错过程,希望能够帮助到正在经历类似问题的你…...
Think | 大模型迈向AGI的探索和对齐
注:节选自我于24年初所写的「融合RL与LLM思想探寻世界模型以迈向AGI」散文式风格文章,感兴趣的小伙伴儿可以访问我的主页置顶或专栏收录,并制作了电子书供大家参考,有需要的小伙伴可以关注私信我,因为属于技术散文风格…...
为什么选择在Facebook投放广告?
2024年了你还没对 Facebook 广告产生兴趣?那你可就亏大了! 今天这篇文章,我们会分享它对你扩大业务的好处。要知道,Facebook 广告凭借它庞大的用户群和先进的定位选项,已经是企业主们有效接触目标受众的必备神器。接下…...
10 ARM 体系
10 ARM 体系 ARM体系1、基本概念1.1 常见的处理器1.2 ARM7三级指令流水线1.3 初识PC寄存器 2、 ARM核的七种工作模式3、ARM核七种异常 ARM体系 1、基本概念 1.1 常见的处理器 PowerPC处理器:飞思卡尔MPC系列 DSP:TI达芬奇系列 FPGA:Xilinx赛灵思的ZYN…...
ubuntu中设置开机自动运行的(sudo)指令
ubuntu版本:22.04.4 在Ubuntu中设置开机自动运行某一条(需要sudo权限的)指令,我们可以通过编辑系统的启动脚本来实现: 创建一个新的启动脚本:创建一个新的脚本文件,并将其放置在 /etc/init.d/ 目…...
删掉Elasticsearch6.x 的 .security-6索引会怎么样?
背景 玩了下 Elasticsearch 的认证,启动 ES 并添加认证后,看到索引列表额外多了一个 .security-6 。以为是没用的,手欠就给删掉了,然后 Elasticsearch 就访问不了了。 只好再重新部署,再看索引内容,发现这…...
Navicat Premium15 下载与安装(免费版)以及链接SqlServer数据库
转自:https://blog.csdn.net/m0_75188141/article/details/139842565...
Vue3配置vite.config.js代理解决跨域问题
前言: 当浏览器发出一个请求时,只要请求URL的协议、域名、端口三者之间任意一个与当前页面URL不同,就称为跨域。 跨域一般出现在开发阶段,由于线上环境前端代码被打包成了静态资源,因而不会出现跨域问题,这篇文章主要给大家介绍了关于Vue3配置vite.config.js解决跨域问题的相…...
Solidity面试题,由浅入深
Solidity是Ethereum智能合约的主要编程语言,面试题的设计旨在评估候选人对Solidity语言特性的掌握程度,以及他们对区块链和智能合约的理解。下面列出了一些常见的Solidity面试题,涵盖基础知识到高级概念,并简要说明每个问题的答案…...
变量的注意或许需要调试
输入一个自然数N(1<N<9),从小到大输出用1~N组成的所有排列,也就说全排列。例如输入3则输出 123 132 213 231 312 321 输入格式: 输入一个自然数N(1<N<9) 输出格式: N的全排列,每行一…...
C# 增删改查教程 代码超级简单
目录 一.留言 二 .帮助类 三 .增删改查代码展示 一.留言 大家好,前几篇文章我们更新了 C# 三层架构的相关代码,主要写了登录,以及增删改查的相关代码,用的三层架构的框架,那么本篇文章一次性更新C#的增删改查相关代…...
OceanBase V4.2特性解析:OB Oracle模式下的 SDO_GEOMETRY 空间数据类型
1. 背景 1.1. SDO_GEOMETRY的应用场景及能力 在数字化城市、物联网和新能源汽车等领域蓬勃发展的背景下,空间数据类型的存储和分析需求日益增长;对于涉及位置信息服务和地理位置信息应用而言,数据库中具备对sdo_geometry数据类型的支持无疑…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
