Java Fluent编程

news2024/11/20 10:29:07

背景

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<>();

    @Override
    public SandwichOrder.BreadOrder sandwich() {
        return new SandwichOrderFluent();
    }

    @Override
    public DrinkOrder.DrinksStyleOrder drink() {
        return new DrinkOrderFluent();
    }

    @Override
    public Checkout checkout() {
        return new Checkout(sandwiches, drinks);
    }

    class DrinkOrderFluent implements Order.DrinkOrder.DrinksStyleOrder,
            Order.DrinkOrder.SizeOrder {
        private Drink drink;

        @Override
        public Order.DrinkOrder.SizeOrder softDrink() {
            drink = new Drink();
            drink.setType(DrinkType.SOFT_DRINK);
            return this;
        }

        @Override
        public Order.DrinkOrder.SizeOrder cocktail() {
            drink = new Drink();
            drink.setType(DrinkType.COCKTAIL);
            return this;
        }

        @Override
        public 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;

        @Override
        public Order.SandwichOrder.SizeOrder bread(BreadType bread) {
            sandwich = new Sandwich();
            sandwich.setBread(bread);
            return this;
        }

        @Override
        public Order.SandwichOrder.StyleOrder size(Size size) {
            sandwich.setSize(size);
            return this;
        }

        @Override
        public Order vegan() {
            sandwich.setStyle(SandwichStyle.VEGAN);
            OrderFluent.this.sandwiches.add(sandwich);
            return OrderFluent.this;
        }

        @Override
        public 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.coloradmin.cn/o/1035348.html

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

相关文章

应急响应学习

网站 首先确定网站的基本信息&#xff0c;如语言&#xff0c;数据库&#xff0c;中间件&#xff0c;CMS&#xff0c;系统等。对网站可能存在的漏洞有初步判断 基于时间分析 询问管理员&#xff0c;确定网站发生异常的时间 查看日志&#xff0c;查看状态码&#xff0c;200为成…

Avl树(有详细图解)

目录 介绍 引入 概念 特点 模拟实现 思路 插入 旋转 左旋 无子树 有子树 右旋 无子树 有子树 左右旋 引入(也就是有子树版本的抽象图解) 解决方法(也就是左右旋) 总结 无子树(也就是curright的位置就是newnode) 有子树 模型高度解释 旋转 更新三个…

如何像开发人员一样思考_成为一个问题解决者

程序员在处理大问题时通常会将其分解成多个小问题来解决。这个过程通常被称为“分解”或“分治”&#xff0c;它是一种将复杂问题分解成可管理的小问题的方法。 以下是程序员思考如何将大问题分解成小问题的一些步骤&#xff1a; 确定问题域&#xff1a;程序员需要了解和理解问…

Kettle REST Client获取token调用接口解析JSON入文件实战

Kettle REST Client通过GET获取token以POST方式请求接口解析JSON入文件完整实例 需求说明 通过kettle组件调用接口并解析JSON成结构化数据入文件。 完整实例 解决方法 利用生成记录组件定义URL参数通过REST ClENT组件请求得到TOKEN通过JSON INPUT组件解析接口请求的结果通过…

vue3新语法糖<script setup>

各种使用方法参考&#xff1a;(184条消息) 【vue3学习系列】组合式api中&#xff0c;替代setup()函数的&#xff1c;script setup&#xff1e;特性写法_庞囧的博客-CSDN博客https://blog.csdn.net/pagnzong/article/details/121733394 一、参考&#xff1a;Vue3 script setup …

Git版本控制:入门到精通

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

简单的自托管书签服务NeonLink

什么是 NeonLink &#xff1f; NeonLink 是一个简单且开源的自托管书签服务。它是轻量级的&#xff0c;使用最少的依赖项&#xff0c;并且易于通过 Docker 安装。由于系统要求较低&#xff0c;该应用程序非常适合部署在 RaspberryPI 上。 安装 在群晖上以 Docker 方式安装。 …

图片可变码怎么做?可编辑二维码制作教程

现在很多人会将拍摄的图片转二维码来展示&#xff0c;这种方法能够有效的让更多人更快的查看图片内容&#xff0c;那么图片生成二维码该怎么做呢&#xff1f;有些小伙伴知道现在二维码可以在图案不变情况下修改内容&#xff0c;如果我们需要不断填充照片&#xff0c;那么这种类…

4.基本IO口操作

CC2530端口资源&#xff1a;三个端口&#xff0c;表示为 P0、P1 和 P2。P0 和 P1 是完全的 8 位端口&#xff0c;而 P2 仅有 5 位可用 CC2530的IO口的一些功能&#xff1a; 通用IO口、外设IO口&#xff08;定时器、USART、ADC&#xff09; 输入引脚、输出引脚 当输入时&#…

奇安信天擎Linux客户端部署相关事项

奇安信天擎Linux客户端部署 一 Linux天擎客户端部署在线部署离线部署 二 Linux 单机部署需要开放的端口三 Linux天擎客户端停止和启动天擎的命令四 Linux天擎客户端卸载五 卸载后检查六 Linux天擎客户端病毒库更新操作步骤七 Linux客户端是否有补丁库&#xff1f; 一 Linux天擎…

设备巡检电力水利物业巡检小程序开源版开发

设备巡检电力水利物业巡检小程序开源版开发 以下是设备巡检电力水利物业巡检小程序开源版的可能功能列表&#xff1a; 用户登录/注册&#xff1a;用户可以通过手机号或其他方式进行登录和注册。 首页展示&#xff1a;展示设备巡检电力水利物业巡检小程序的基本信息和操作指南…

6.1 KMP算法搜索机器码

KMP算法是一种高效的字符串匹配算法&#xff0c;它的核心思想是利用已经匹配成功的子串前缀的信息&#xff0c;避免重复匹配&#xff0c;从而达到提高匹配效率的目的。KMP算法的核心是构建模式串的前缀数组Next&#xff0c;Next数组的意义是&#xff1a;当模式串中的某个字符与…

【力扣】83. 删除排序链表中的重复元素

题目描述 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,1,2] 输出&#xff1a;[1,2] 示例 2&#xff1a; 输入&#xff1a;head [1,1,2,3,3] 输…

笔试强训Day(一)

T1&#xff1a;组队竞赛 链接&#xff1a;组队竞赛__牛客网 牛牛举办了一次编程比赛,参加比赛的有3*n个选手,每个选手都有一个水平值a_i.现在要将这些选手进行组队,一共组成n个队伍,即每个队伍3人.牛牛发现队伍的水平值等于该队伍队员中第二高水平值。 例如: 一个队伍三个队员…

【DDPM论文解读】Denoising Diffusion Probabilistic Models

0 摘要 本文使用扩散概率模型合成了高质量的图像结果&#xff0c;扩散概率模型是一类受非平衡热力学启发的潜变量模型。本文最佳结果是通过根据扩散概率模型和朗之万动力学的去噪分数匹配之间的新颖联系设计的加权变分界进行训练来获得的&#xff0c;并且本文的模型自然地承认…

Jupyter Notebook中的魔法命令

关于魔术命令 Jupyter Notebook 使用的 Python 内核通常是 IPython 内核。IPython 是 Python 的增强交互式解释器&#xff0c;它提供了许多额外的功能&#xff0c;使得在 Jupyter Notebook 中编写和执行 Python 代码更加方便和强大。所以jupyter使用的是IPython的语法 IPytho…

彩色图像处理在数字图像处理中的应用(数字图像处理概念 P5)

文章目录 彩色模型伪彩色处理全彩色数字图像处理基础彩色变换平滑和锐化 彩色模型 伪彩色处理 全彩色数字图像处理基础 彩色变换 平滑和锐化

有名管道及其应用

创建FIFO文件 1.通过命令&#xff1a; mkfifo 文件名 2.通过函数: mkfifo #include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode); 参数&#xff1a; -pathname&#xff1a;管道名称的路径 -mode&#xff1a;文件的权限&a…

MySQL学习笔记5

1、MySQL中的SQL语句&#xff1a; SQL 是 Structure Query Language(结构化查询语言)的缩写,它是使用关系模型的数据库应 用语言,由 IBM 在 20 世纪 70 年代开发出来,作为 IBM 关系数据库原型 System R 的原型关 系语言,实现了关系数据库中的信息检索。 20 世纪 80 年代初,美…

【ardunio】青少年机器人四级实操代码(2023年9月)

目录 一、题目 二、示意图 三、流程图 四、硬件连接 1、舵机 2、超声波 3、LED灯 五、程序 一、题目 实操考题(共1题&#xff0c;共100分) 1. 主题&#xff1a; 迎宾机器人 器件&#xff1a;Atmega328P主控板1块&#xff0c;舵机1个&#xff0c;超声波传感器1个&…