Java Fluent编程

背景

Fluent Api最早是由Martin Fowler1提出的一种简洁的编程风格, 其每一步的执行都返回一个对象并可用于进一步的方法调用. 这样的编程风格可以很好的简化某些领域的开发, 并显著地提高代码的可读性和可维护性. 无论是在Java的流式api中, 还是众多DLS中都有它的身影. 原因主要有以下几点:

  • 代码简化: 流式编程提供了一种链式调用的方法, 可以更加灵活地将多个操作组合在一起, 减少代码量
  • 代码封装: 流式编程可以将某些复杂操作封装起来, 屏蔽其实现细节, 进而带来良好的可维护性
  • 延迟计算: 流式编程中往往只有到达了某个状态才开始计算, 其过程可以对执行链路进行优化, 进而带来更好的系统性能

功能实现

流式编程在java中的示例: Stream

在java中, Stream是流式编程的一个非常典型的例子, 甚至"Stream"同"Fluent"一样都可以翻译成"流". 以下面的代码为例:

    public static void main(String[] args) {int[] array = new Random().ints().limit(100).filter(x -> x > 10).sorted().toArray();System.out.println(Arrays.toString(array));}

上述例子可以非常容易地过滤掉100个小于10的随机数, 如果我们想要添加排序功能, 可以通过简单添加parallel一个关键字实现:

    public static void main(String[] args) {int[] array = new Random().ints().parallel().limit(100).filter(x -> x > 10).sorted().toArray();System.out.println(Arrays.toString(array));}

可见流式编程能够非常有效地简化代码, 进而带来非常好的可维护性.

一个订单的例子2

流式编程的本质其实是状态的转换[^3], 以餐馆销售三明治为例, 实现通过流式api购买三明治.2

餐馆的构成很简单, 可以购买三明治和饮料, 三明治和饮料又分别有不同的配料和种类; 在选择这些种类的时候应支持Fluent api风格. 主要需要实现的有以下几点:

  • 餐馆可以购买三明治, 三明治可以选择大小, 面包和馅料, 餐馆根据三明治的配料决定价格
  • 餐馆可以购买饮料, 饮料可以选择种类和大小, 餐馆可以根据饮料的种类决定价格
  • 客户可以购买无限多的三明治和饮料, 所有的三明治和饮料必须由上述三个部分组成
  • 餐馆可以打印账单

据此可以画出状态机图:
在这里插入图片描述

状态机实现

根据上面状态机图, 编写相应的接口

package com.passnight.javanote.design.fluent.sandwich;public interface Order {interface SandwichOrder {interface BreadOrder {SizeOrder bread(BreadType bread);}interface SizeOrder {StyleOrder size(Size size);}interface StyleOrder {Order vegan();Order meat();}}interface DrinkOrder {interface DrinksStyleOrder {SizeOrder softDrink();SizeOrder cocktail();}interface SizeOrder {Order size(Size size);}}Order.SandwichOrder.BreadOrder sandwich();Order.DrinkOrder.DrinksStyleOrder drink();Checkout checkout();
}

状态转换的实现

编写OrderFluent, DrinkOrderFluent, SandwichOrderFluent类实现上面的接口, 这里注意 DrinkOrderFluentSandwichOrderFluentOrderFluent之间是组合的关系, 因为内部类的生命周期依赖于外部类而存在

package com.passnight.javanote.design.fluent.sandwich;import java.util.ArrayList;
import java.util.List;class OrderFluent implements Order {List<Sandwich> sandwiches = new ArrayList<>();List<Drink> drinks = new ArrayList<>();@Overridepublic SandwichOrder.BreadOrder sandwich() {return new SandwichOrderFluent();}@Overridepublic DrinkOrder.DrinksStyleOrder drink() {return new DrinkOrderFluent();}@Overridepublic Checkout checkout() {return new Checkout(sandwiches, drinks);}class DrinkOrderFluent implements Order.DrinkOrder.DrinksStyleOrder,Order.DrinkOrder.SizeOrder {private Drink drink;@Overridepublic Order.DrinkOrder.SizeOrder softDrink() {drink = new Drink();drink.setType(DrinkType.SOFT_DRINK);return this;}@Overridepublic Order.DrinkOrder.SizeOrder cocktail() {drink = new Drink();drink.setType(DrinkType.COCKTAIL);return this;}@Overridepublic Order size(Size size) {drink.setSize(size);OrderFluent.this.drinks.add(drink);return OrderFluent.this;}}class SandwichOrderFluent implements Order.SandwichOrder.BreadOrder,Order.SandwichOrder.SizeOrder,Order.SandwichOrder.StyleOrder {private Sandwich sandwich;@Overridepublic Order.SandwichOrder.SizeOrder bread(BreadType bread) {sandwich = new Sandwich();sandwich.setBread(bread);return this;}@Overridepublic Order.SandwichOrder.StyleOrder size(Size size) {sandwich.setSize(size);return this;}@Overridepublic Order vegan() {sandwich.setStyle(SandwichStyle.VEGAN);OrderFluent.this.sandwiches.add(sandwich);return OrderFluent.this;}@Overridepublic Order meat() {sandwich.setStyle(SandwichStyle.MEAT);OrderFluent.this.sandwiches.add(sandwich);return OrderFluent.this;}}
}

餐馆的实现

在定义了状态转换之后, 需要定义三明治以及饮料的种类

public enum BreadType {ITALIAN, PLAIN, GLUTEN_FREE
}public enum SandwichStyle {VEGAN, MEAT
}public enum Size {SMALL, MEDIUM, LARGE
}public enum DrinkType {SOFT_DRINK, COCKTAIL
}

之后是三明治和饮料的定义

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Sandwich {SandwichStyle style;BreadType bread;Size size;
}@Data
@AllArgsConstructor
@NoArgsConstructor
public class Drink {DrinkType type;Size size;
}

有了三明治和饮料之后, 餐厅需要有菜单标注其价格, 这里菜单使用enum的单例模式, java虚拟机自动帮助我们完成单例对象的线程安全的懒加载

enum Menu {INSTANCE;private final Map<Size, Double> sizePrice;private final Map<SandwichStyle, Double> stylePrice;private final Map<DrinkType, Double> drinkPrice;private final Map<BreadType, Double> breadPrice;Menu() {this.sizePrice = new EnumMap<>(Size.class);this.sizePrice.put(Size.SMALL, 1d);this.sizePrice.put(Size.MEDIUM, 5d);this.sizePrice.put(Size.LARGE, 10d);this.stylePrice = new EnumMap<>(SandwichStyle.class);this.stylePrice.put(SandwichStyle.MEAT, 10d);this.stylePrice.put(SandwichStyle.VEGAN, 12d);this.drinkPrice = new EnumMap<>(DrinkType.class);this.drinkPrice.put(DrinkType.SOFT_DRINK, 1d);this.drinkPrice.put(DrinkType.COCKTAIL, 6d);this.breadPrice = new EnumMap<>(BreadType.class);this.breadPrice.put(BreadType.PLAIN, 1d);this.breadPrice.put(BreadType.ITALIAN, 2d);this.breadPrice.put(BreadType.GLUTEN_FREE, 3d);}double getPrice(DrinkType type) {return ofNullable(this.drinkPrice.get(type)).orElseThrow(() -> new IllegalArgumentException("There is not price to the drink " + type));}double getPrice(BreadType bread) {return ofNullable(this.breadPrice.get(bread)).orElseThrow(() -> new IllegalArgumentException("There is not price to the bread " + bread));}double getPrice(SandwichStyle style) {return ofNullable(this.stylePrice.get(style)).orElseThrow(() -> new IllegalArgumentException("There is not price to the sandwich style " + style));}double getPrice(Size size) {return ofNullable(this.sizePrice.get(size)).orElseThrow(() -> new IllegalArgumentException("There is not price to the size " + size));}
}

最后是订单, 可以打印最终的价格已经购买的菜品

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Checkout {private final static Menu menu = Menu.INSTANCE;List<Sandwich> sandwiches = new ArrayList<>();List<Drink> drinks = new ArrayList<>();public double checkout() {return sandwiches.stream().mapToDouble(sandwich -> menu.getPrice(sandwich.getBread()) + menu.getPrice(sandwich.getStyle()) + menu.getPrice(sandwich.getSize())).sum()+drinks.stream().mapToDouble(drink -> menu.getPrice(drink.getType()) + menu.getPrice(drink.getSize())).sum();}public String receipt() {return "sandwiches\n"+ sandwiches.stream().map(sandwich -> sandwich.toString() + ": " + (menu.getPrice(sandwich.getBread()) + menu.getPrice(sandwich.getStyle()) + menu.getPrice(sandwich.getSize()))).collect(Collectors.joining("\n"))+ "\ndrinks\n"+ drinks.stream().map(drink -> drink.toString() + ": " + (menu.getPrice(drink.getType()) + menu.getPrice(drink.getSize()))).collect(Collectors.joining("\n"));}
}

以下是一个测试类

public class Restaurant {public static void main(String[] args) {Checkout checkout = new OrderFluent().drink().cocktail().size(Size.LARGE).drink().cocktail().size(Size.MEDIUM).drink().softDrink().size(Size.SMALL).sandwich().bread(BreadType.ITALIAN).size(Size.LARGE).vegan().sandwich().bread(BreadType.GLUTEN_FREE).size(Size.LARGE).meat().sandwich().bread(BreadType.PLAIN).size(Size.LARGE).meat().checkout();System.out.println(checkout.receipt());System.out.println(checkout.checkout());}
}

运行结果如下

sandwiches
Sandwich(style=VEGAN, bread=ITALIAN, size=LARGE): 24.0
Sandwich(style=MEAT, bread=GLUTEN_FREE, size=LARGE): 23.0
Sandwich(style=MEAT, bread=PLAIN, size=LARGE): 21.0
drinks
Drink(type=COCKTAIL, size=LARGE): 16.0
Drink(type=COCKTAIL, size=MEDIUM): 11.0
Drink(type=SOFT_DRINK, size=SMALL): 2.0
97.0

引用

引用


  1. Fluent interface - Wikipedia ↩ ↩︎

  2. Fluent-API: Creating Easier, More Intuitive Code with a Fluent API | by Otavio Santana | xgeeks | Medium
    [^ 3]: https://blog.csdn.net/significantfrank/article/details/104996419 ↩︎ ↩︎

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

备受以太坊基金会青睐的 Hexlink,构建亿级用户涌入 Web3的入口

早在 2021 年 9 月&#xff0c;以太坊创始人 Vitalik Buterin 就曾提出了 EIP-4337&#xff08;账户抽象&#xff09;提案&#xff0c;并在去年 10 月对该提案进一步更新&#xff0c;引发行业的进一步关注。在今年 3 月&#xff0c;EIP-4337 提案正式通过审计&#xff0c;并成为…

SpringBean的生命周期

SpringBean的生命周期 SperingBean的生命周期是从Bean实例化之后&#xff0c;即通过反射创建出对象之后&#xff0c;到Bean成为一个完整对象&#xff0c;最终存储到单例池中&#xff0c;这个过程被称为Spring Bean的生命周期。Spring Bean的生命周期大体上分为三个阶段 Bean的…

初识Java 10-3 集合

目录 Collection和Iterator的对比 for-in和迭代器 总结图 本笔记参考自&#xff1a; 《On Java 中文版》 Collection和Iterator的对比 Collection是所有序列集合的共同根接口。因此&#xff0c;可以认为它是一个为表示其他接口之间的共性而出现的“附属接口”。 java.util.Ab…

Git大全

目录 一、Git概述 1.1Git简介 1.2Git工作流程图 1.3查看Git的版本 1.4 Git 使用前配置 1.5为常用指令配置别名&#xff08;可选&#xff09; 1.5.1打开用户目录&#xff0c;创建 .bashrc 文件 1.5.2在 .bashrc 文件中输入如下内容&#xff1a; 1.5.3打开gitBash&#xff0c;执行…

C++:类中的静态成员函数以及静态成员变量

一、静态成员变量 静态成员&#xff1a;在类定义中&#xff0c;它的成员&#xff08;包括成员变量和成员函数&#xff09;&#xff0c;这些成员可以用关键字static声明为静态的&#xff0c;称为静态成员。 静态成员变量需要在类外分配空间&#xff0c;static 成员变量是在初始…

【解决方案】edge浏览器批量添加到集锦功能消失的解决方案

edge的集锦功能很好用&#xff0c;右键标签页会出现如下选项&#xff1a; 但在某次edge更新后&#xff0c;右键标签页不再出现该选项&#xff1a; 这里可以参考为什么我的Edge浏览器右键标签页没有“将所有标签页添加到集锦”功能&#xff1f; - Microsoft Community 一文提出…

pcl--第十节 点云曲面重建

曲面重建技术在逆向工程、数据可视化、机器视觉、虚拟现实、医疗技术等领域中得到了广泛的应用 。 例如&#xff0c;在汽车、航空等工业领域中&#xff0c;复杂外形产品的设计仍需要根据手工模型&#xff0c;采用逆向工程的手段建立产品的数字化模型&#xff0c;根据测量数据建…

超级好用绘图工具(Draw.io+Github)

超级好用绘图工具&#xff08;Draw.ioGithub&#xff09; 方案简介 绘图工具&#xff1a;Draw.io 存储方式&#xff1a; Github 1 Draw.io 1.2 简介 ​ 是一款免费开源的在线流程图绘制软件&#xff0c;可以用于创建流程图、组织结构图、网络图、UML图等各种类型的图表。…

windows server 2019 、2012等服务器查看系统和应用程序日志

查看windows系统日志 点击左下角的windows按钮&#xff0c;输入事件两个字&#xff0c;会显示时间查看器 点击事件查看器&#xff0c;windows日志下面可以卡到系统日志和应用程序的日志 筛选时间范围内的日志 修改记录时间 选组自定义范围 选择事件事件 输入事件范围&#xff…

C语言大佬的必杀技---宏的高级用法

C语言大佬的必杀技—宏的高级用法 目录: 字符串化标记的拼接宏的嵌套替换多条语句防止一个文件被重复包含宏和函数的区别 可能大家在学习的时候用得比较少&#xff0c;但是在一些代码量比较大的时候&#xff0c;这样使用&#xff0c;可以大大的提高代码的可读性&#xff0c;…

从零开始:新手快速在国产操作系统中搭建高可用K8S(V1.28)集群落地实践

微信改版了&#xff0c;现在看到我们全凭缘分&#xff0c;为了不错过【全栈工程师修炼指南】重要内容及福利&#xff0c;大家记得按照上方步骤设置「接收文章推送」哦~ 关注【公众号】回复【学习交流群】加入【SecDevOps】学习交流群! 文章目录&#xff1a; 本文为作者原创文章…

day03_基础语法

今日内容 零、复习昨日 一、Idea安装&#xff0c;配置 二、Idea使用 三、输出语句 四、变量 五、数据类型 附录: 单词 零、 复习昨日 1 装软件(typora,思维导图) 2 gpt(学会让他帮你解决问题) 3 java发展(常识) 4 HelloWorld程序 5 编码规范 6 安装jdk,配置环境变量 电脑常识 任…

STM32-无人机-电机-定时器基础知识与PWM输出原理

电机控制基础——定时器基础知识与PWM输出原理 - 掘金单片机开发中&#xff0c;电机的控制与定时器有着密不可分的关系&#xff0c;无论是直流电机&#xff0c;步进电机还是舵机&#xff0c;都会用到定时器&#xff0c;比如最常用的有刷直流电机&#xff0c;会使用定时器产生PW…

在GIS(地理信息系统)中,常见的地理文件记录

在GIS&#xff08;地理信息系统&#xff09;中&#xff0c;常见的地理文件包括以下几种&#xff1a; .cpg&#xff08;Code Page文件&#xff09;&#xff1a;这个文件是指定地理数据文件编码的文件&#xff0c;它告诉软件如何正确地读取和解释地理数据文件中的字符编码。比如…

海外代理IP是什么?如何使用?

一、海外代理IP是什么&#xff1f; 首先&#xff0c;代理服务器是在用户和互联网之间提供网关的系统或路由器。它是一个服务器&#xff0c;被称为“中介”&#xff0c;因为它位于最终用户和他们在线访问的网页之间。 海外IP代理是就是指从海外地区获取的IP地址&#xff0c;用…

Linux学习-HIS系统部署(1)

Git安装 #安装中文支持&#xff08;选做&#xff09; [rootProgramer ~]# echo $LANG #查看当前系统语言及编码 en_US.UTF-8 [rootProgramer ~]# yum -y install langpacks-zh_CN.noarch #安装中文支持 [rootProgramer ~]# vim /etc/locale.co…

差值结构的顺序偏好

( A, B )---3*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有3个节点&#xff0c;AB训练集各由5张二值化的图片组成&#xff0c;让A 中有5个点&#xff0c;B中有1个点&#xff0c;且不重合&#xff0c;统计迭代次数并排序。 第一种情况 差值结构 迭代次数 L E - - 2 10491.…

python随手小练1

题目&#xff1a; 使用python做一个简单的英雄联盟商城登录界面 具体操作&#xff1a; print("英雄联盟商城登录界面") print("~ * "*15 "~") #找其规律 a "1、用户登录" b "2、新用户注册" c "3、退出系统&quo…

手动部署 OceanBase 集群

手动部署一个 OB 单副本集群&#xff0c;包括一个 OBProxy 节点 部署环境 服务器信息 IP地址 192.168.0.26 网卡名 ifcfg-enp1s0 OS Kylin Linux Advanced Server release V10 CPU 8C 内存 32G 磁盘1 本地盘 /data/1 磁盘2 本地盘 /data/log1 机器和角色划分 …

es小记(copy_to)

简单创建索引复制字段 1: 3个主分片,各自有一个副本,总分片数为 3*26; refresh_interval为刷新频率; 其他参数描述,转载自 PUT test1 { “settings”:{ “number_of_shards”: 1, “number_of_replicas”: 1, “refresh_interval”: “30s” }, “mappings”:{ “properties”…