java与es8实战之一:以builder pattern开篇

这篇具有很好参考价值的文章主要介绍了java与es8实战之一:以builder pattern开篇。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

关于《java与es8实战》系列

  • 《java与es8实战》系列是欣宸与2022年夏季推出的原创系列,如标题所述,该系列从一个java程序员视角去学习和实践elasticsearch的8.2版本,目标是与大家一起掌握与elasticsearch开发相关的技能,以应对实际应用中的需求和挑战

本篇概览

  • 纵观欣宸过往各种系列文章,开篇无外乎两种套路
  1. 第一种是对该系列的主题做重点介绍,把重点、背景说清楚
  2. 第二种更加实在,就是准备工作,例如安装相关的软件,介绍对应版本,甚至写个初级的hello world
  • 那么《java与es8实战》系列的开篇应该是哪种风格?是介绍elasticsearch?还是动手部署一套es集群?亦或是用java写一套简单的增删改查代码,让大家可以快速入门?
  • 这个问题难住我了,思考良久,想到刚开始写es代码时的困惑,那时去看es的java库源码中的单元测试部分,研究如何调用java库的api,看到那里是这么写代码的,先是创建索引,创建请求对象会用到builder
java与es8实战之一:以builder pattern开篇
  • 再随意逛到了批量操作的代码,如下图,还是builder
java与es8实战之一:以builder pattern开篇
  • 最常用的聚合查询,如下图,也离不开builder
java与es8实战之一:以builder pattern开篇
  • 于是我就纳闷了:以后写es相关的代码,这builder操作难道会一直伴随我?
  • 去翻阅es的官方文档,发现说的很清楚:Java客户端中的数据对象都是不可变的,这些数据对象在创建时用的是2008版本《Effective Java》中的builder模式
java与es8实战之一:以builder pattern开篇
  • 回忆了这么多,我终于想清楚《java与es8实战》的开篇内容了:咱们不急着部署ES,也不急着写增删改查的入门级代码,今天,欣宸邀您一同去温习经典,搞清楚以下问题:
  1. 直接用构造方法创建对象有什么问题?
  2. 用静态方法创建对象有什么问题?
  3. builder模式是什么?
  4. builder模式解决了什么问题?
  5. builder模式自己有啥问题?
  6. es API和builder有啥关系?
  • 等咱们搞清楚这些问题,写代码操作es时遇到builder就不再疑惑,而是感受到builder带来的好处,进而养成习惯,在今后设计不可变类时自然而然的用上builder模式,那时候您不一定还在用es,然而builder模式可以长久陪伴您,因为,经典就是经典,如下图
java与es8实战之一:以builder pattern开篇
  • 现在,咱们java程序员的es8开发之旅,就从经典的builder pattern出发

不可变对象(Immutable Objects)

  • es的API中的对象都是不可变的(immutable),关于不可变,简单的说就是:实例一旦创建后,不能改变其成员变量的值
  • 本篇文章讨论的创建对象,都是指的不可变对象

三种创建对象的常用方法

  • 这三种分别是
  1. 构造方法
  2. 静态工厂方法
  3. builder模式

直接用构造方法创建对象有什么问题

  • 创建一个对象,最常用的方法不就是构造方法么?new Object()不香吗?

  • 成员变量很多的时候,构造方法就没那么香了,举例如下,NutritionFacts是食品包装外面显示的营养成分标签,这里面有的营养成分是必须的:每一份的含量、每一罐的含量,其他的可选

public class NutritionFacts {
    private final int servingSize;  // (mL)            required
    private final int servings;     // (per container) required
    private final int calories;     //                 optional
    private final int fat;          // (g)             optional
    private final int sodium;       // (mg)            optional
    private final int carbohydrate; // (g)             optional

    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }

    public NutritionFacts(int servingSize, int servings,
            int calories) {
        this(servingSize, servings, calories, 0);
    }

    public NutritionFacts(int servingSize, int servings,
            int calories, int fat) {
        this(servingSize, servings, calories, fat, 0);
    }

    public NutritionFacts(int servingSize, int servings,
            int calories, int fat, int sodium) {
        this(servingSize, servings, calories, fat, sodium, 0);
    }

    public NutritionFacts(int servingSize, int servings,
           int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize  = servingSize;
        this.servings     = servings;
        this.calories     = calories;
        this.fat          = fat;
        this.sodium       = sodium;
        this.carbohydrate = carbohydrate;
    }
}
  • 从上面的代码可见,为了尽量满足用户需要,NutritionFacts提供了多个构造方法给用户使用,其实相信您也明白这里面的问题:这简直是成员变量的各种排列组合呀,以后要是加字段就麻烦了
  • 再以一个使用者的视角来看看,实例化代码如下,这就有点晕了,这一眼看过去,谁知道240给了哪个字段?只能去核对构造方法的入参声明
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
  • 缓解上述问题的一种方法是使用JavaBeans模式,用无参构造方法,然后按照调用setXXX设置每个所需字段,示例如下所示
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
  • 上述方法似乎不错,哪些字段被设置一目了然,所以,成员变量多的时候,用上述方法是正确选择吗?
  • 然而,《Effective Java》原著对上述做法的评价是有着严重的弊端(the JavaBeans pattern has serious disadvantages of its own),所以,尽早放弃吧...咱们来看看具体有啥问题
  1. 首先,直观的看,这种做法违背了不可变对象的定义,创建出对象后,又用setXXX方法改变了成员变量
  2. 《Effective Java》的原话是在构造过程中JavaBean可能处于不一致的中的状态,我的理解如下图所示,不用颜色代表不同线程,可以看到,红色线程获取calories的值的时候,蓝色线程还没有开始设置calories的值,所以红色线程拿到的等于初始值0,这显然是不对的,正常逻辑应该是:只要cocaCola对象非空,其calories字段对外显示的值就是100
java与es8实战之一:以builder pattern开篇
  1. 经验丰富的您应该想到了这是典型的线程同步问题,应该用synchronize或ReentrantLock给蓝色代码段加锁,让红色代码先block住,直到蓝色代码执行完毕,这样就能拿到正确的值了---这种方法显然可以解决问题,然而《Effective Java》预判了您的预判:这种方式十分笨拙,在实践中很少使用,想想也是,创建和使用对象是最常见的编码了,这个思路要加多少synchronize或ReentrantLock
  • 所以构造方法不能满足我们的实际需要,再来看看静态工厂方法,它的优势在哪里

静态工厂方法的优势

  • 相比静态工厂方法,构造方法存在以下五个典型问题
  1. 随着入参的不同,构造方法可以有多个,如下所示,然而都是同名的,这会给用户造成困惑,此刻用静态工厂方法,可以自由设置方法名(例如createWithName或者createWithAge),让用户更方便的选择合适的方法
    public Student(String name) {
        this.name = name;
    }

    public Student(int age) {
        this.age = age;
    }
  1. 使用构造方法意味着创建对象,而有时候我们只想使用,并不在乎对象本身是否是新建的,下面是Boolean.valueOf方法的源码,此处并未新建Boolean对象:
    public static Boolean valueOf(String s) {
        return parseBoolean(s) ? TRUE : FALSE;
    }
  1. 以动物类Animal.class为例,Animal类的构造方法创建的对象Animal的实例,而静态工厂方法的返回值声明虽然是Animal,但实际返回的实例可以是Animal的子类,例如Dog
  2. 静态工厂方法内部可以有灵活的逻辑来决定返回那种子类的实例,来看的静态工厂方法源码,根据底层枚举类型的大小来决定是返回RegularEnumSet实例还是JumboEnumSet实例
    public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        Enum<?>[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + " not an enum");

        if (universe.length <= 64)
            return new RegularEnumSet<>(elementType, universe);
        else
            return new JumboEnumSet<>(elementType, universe);
    }
  1. 静态工厂方法还有一个优势:方法返回对象所属的类,在编写此静态方法时可以不存在,这句话有点晦涩,可以回想一下JDBC的获取connection的API,在编写此API的时候,并不需要知道MySQL的driver实现
  • 以上的比较暴露出构造方法的缺陷,此时静态工厂方法更加合适,然而,静态工厂方法就这么完美吗?

静态工厂方法的不足

  • 只有最合适的,没有最好的,静态工厂方法也有自己的不足
  1. 当您开发一个类时,如果决定对外提供静态工厂方法,那么将构造方法设为私有,就可以让用户只能选择静态工厂方法了,代码如下所示,然而,这样的Student类就无法被继承
public class Student {
    private String name;

    private int age;

    public void setName(String name) {
        this.name = name;
    }

    private Student() {

    }

    public static Student newInstance(String name) {
        Student student = new Student();
        student.setName(name);

        return student;
    }
}
  1. 一个类的代码中,可能已有一些静态方法,再加入静态工厂方法,一堆静态方法混杂在一起,用户从中找出静态工厂方法怕是不容易

builder pattern

  • 看过了构造方法和静态工厂方法,认识到它们的不足,终于该第三种方法登场了
  • builder pattern,《Effective Java》中文版译作建造者模式,用builder对象来创建真正的对象实例,前面提到的构造方法和静态工厂的不足,在builder pattern这里都得到了改善
  • 来看代码吧,以刚才的NutritionFacts为例,使用builder pattern后的代码如下,新增一个静态成员类Builder,可以设置Builder的每个成员变量,最后调用其build方法的时候,才真正创建NutritionFacts对象
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // Required parameters
        private final int servingSize;
        private final int servings;

        // Optional parameters - initialized to default values
        private int calories      = 0;
        private int fat           = 0;
        private int carbohydrate  = 0;
        private int sodium        = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings    = servings;
        }

        public Builder calories(int val)
            { calories = val;      return this; }
        public Builder fat(int val)
            { fat = val;           return this; }
        public Builder carbohydrate(int val)
            { carbohydrate = val;  return this; }
        public Builder sodium(int val)
            { sodium = val;        return this; }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}
  • 以一个使用者的视角来看如何创建NutritionFacts对象,如下所示,流畅的写法,那些字段被设置以及具体的值都一目了然,最终build方法才会创建NutritionFacts对象,而且这是个不可变对象
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
                              .calories(100)
                              .sodium(35)
                              .carbohydrate(27)
                              .build();

builder pattern自身的问题和适用场景

  • 即便能解决构造方法和静态工厂自身的一些问题,builder pattern也不是万能的,缺点很明显:创建对象之前,先要创建builder对象,这在一些性能要求高、资源限制苛刻的场景中就不适合了
  • 另外builder pattern适合的场景是成员变量多的时候,而这个所谓的究竟如何理解呢?这可能是个小马过河的问题吧:见惯了几十个成员变量的类,再去看十几个成员变量的类,可能会有种很清爽的感觉,呃,扯远了,其实《Effective Java》的说法是四个或者更多个参数,就适合用builder apttern了

elasticsearch API中的builder

  • 终于到达重点了:接下来的es之旅,会遇到什么样的builder?咱们该怎么用它?
  • 先总结builder的使用套路,其实在es中的builder也是按照套路去用的,如下图,其实很简单,三步走而已,暂时把下图称为套路图,后面还会提到
java与es8实战之一:以builder pattern开篇
  • 看看es API的用法,以es自己的单元测试代码为例,如下图所示,创建一个索引时,会指向红色箭头所指的create方法

java与es8实战之一:以builder pattern开篇

  • 来看看create的源码,入参是个Function,里面执行了function的apply,这是个典型的lambda表达式作为入参
	public final CreateIndexResponse create(Function<CreateIndexRequest.Builder, ObjectBuilder<CreateIndexRequest>> fn)
			throws IOException, ElasticsearchException {
		return create(fn.apply(new CreateIndexRequest.Builder()).build());
	}
  • Function的两个泛型,第一个表示入参,第二个表示返回,对于create方法的用户来说,这就有意思了:
  1. 咱们在写这个lambda表达式时,入参是builder对象,这可以从上面的代码中看到(即apply方法的入参),也就是说套路图中的第一步:创建builder对象,已经被create方法内部做好了
  2. 再看看上面的截图中,lambda表达式做了什么? b.index("my-index"),这里可以按照实际业务需要调用builder的多个方法来来设置参数,所以套路图中的第二步,需要咱们在lambda表达式中完成,这很合理:需要设置哪些参数只有用户最清楚
  3. 最后,也是最巧妙的地方,就是上面的create方法源码中的.build(),因为fn.apply方法的实现是调用者写的,例如刚才写的是 b.index("my-index"),这个index方法的返回值就是build实例,所以fn.apply(xxx).build()就是套路图中的第三步:builder的build方法被执行了,真正的对象,即CreateIndexRequest对象此刻被创建,这也被es内部给做好了
  • 小结如下图
    java与es8实战之一:以builder pattern开篇
  • 看到这里,不知您是否会击掌叫好,builder与lambda的巧妙结合,整个套路中,第二步留给使用者按需定制,而固定的第一和第三步都被es自己实现,对使用者来说显得非常精简,而整个过程并无特殊之处,都是对经典的娴熟应用
  • 经历了本文,今后在写es操作代码时,面对各种builder和lambda,相信您不再迷茫,取而代之的是模式的欣赏和品味,以及本就该如此的感悟
  • 网络上写es开发的系列文章并不少,像欣宸这样拿builder做开篇的,应该独一无二了...吧
  • 好了,《java与es8实践》的画卷已顺利展开一角,接下来,请允许欣宸原创继续陪伴您,像今天这样踏踏实实,一步一个脚印,从入门到精通

欢迎关注博客园:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...文章来源地址https://www.toymoban.com/news/detail-665611.html

到了这里,关于java与es8实战之一:以builder pattern开篇的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • java与es8实战之三:Java API Client有关的知识点串讲

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇是《java与es8实战》系列的第三篇,将一些重要的知识点在这里梳理清楚,为后面的实践奠定基础 一共有七个与Java API Client有关的重要知识点 关于namespace:每个feature都有自己的package 命名规则:

    2024年02月11日
    浏览(43)
  • Java设计模式之建造者模式详解(Builder Pattern)

    在日常的开发工作中,我们常常需要创建一些复杂的对象。这些对象可能包含许多不同的属性,并且这些属性的初始化过程可能相当复杂。在这种情况下,建造者模式是一种非常有用的设计模式,因为它允许我们分步骤地创建复杂的对象。 概念和原理: 建造者模式(Builder

    2024年02月09日
    浏览(44)
  • ES8 向量搜索(knn-search)java-api 实践

    官方文档-knn-search kNN搜索 k-nearest neighbor (kNN)搜索找到与查询向量最近的k个向量,如通过相似度计算。 kNN的常见用例包括: 基于自然语言处理(NLP)算法的相关性排序 产品推荐和推荐引擎 图像或视频的相似性搜索 要运行kNN搜索,您必须能够将数据转换为有意义的向量值

    2024年02月12日
    浏览(46)
  • 最新版ES8的client API操作 Elasticsearch Java API client 8.0

    作者:ChenZhen 本人不常看网站消息,有问题通过下面的方式联系: 邮箱:1583296383@qq.com vx: ChenZhen_7 我的个人博客地址:https://www.chenzhen.space/🌐 版权:本文为博主的原创文章,本文版权归作者所有,转载请附上原文出处链接及本声明。📝 如果对你有帮助,请给一个小小的s

    2024年02月04日
    浏览(41)
  • 建造者模式-Builder Pattern

    原文地址:https://jaune162.blog/design-pattern/builder-pattern/ 现在一般大型的业务系统中的消息通知的形式都会有多种,比如短信、站内信、钉钉通知、邮箱等形式。虽然信息内容相同,但是展现形式缺不同。如短信使用的是纯文本的形式,钉钉使用的一般是Markdown的形式,而邮箱则

    2024年02月20日
    浏览(35)
  • 建造者模式(Builder Pattern)

    建造者模式(Builder Pattern)是最复杂的创建型模式,它 用于创建一个包含多个组成部分的复杂对象 ,可以返回一个完整的产品对象给用户。它通过将 客户端与包含多个组成部分的复杂对象的创建过程分离 ,使得 客户端无需知道复杂对象的内部组成部分与装配方式,只需要

    2024年02月03日
    浏览(62)
  • Builder Pattern —— Structure Class

    建造者模式又称为 生成器模式 ,主要用于对复杂对象的构建、初始化,它可以 将多个简单的组件对象按顺序一步步组装起来 , 最终构建成一个复杂的成品对象 。 与工厂系列模式不同的是,建造者模式的主要目的在于把烦琐的 构建过程 从不同对象中抽离出来,使其脱离并

    2024年02月10日
    浏览(34)
  • 设计模式--建造者模式(Builder Pattern)

    建造者模式(Builder Pattern)是一种创建型设计模式,它关注如何按照一定的步骤和规则创建复杂对象。建造者模式的主要目的是将一个复杂对象的构建过程与其表示分离,从而使同样的构建过程可以创建不同的表示。 在建造者模式中,通常有以下几个核心角色: 产品(Prod

    2024年02月11日
    浏览(38)
  • 设计模式——建造者模式(Builder Pattern)

    概述        建造者模式是较为复杂的创建型模式,它将客户端与包含多个组成部分(或部件)的复杂对象的创建过程分离,客户端无须知道复杂对象的内部组成部分与装配方式,只需要知道所需建造者的类型即可。它关注如何一步一步创建一个的复杂对象,不同的具体建

    2024年01月20日
    浏览(48)
  • 设计模式|建造者模式(Builder Pattern)

    建造者模式(Builder Pattern)是一种创建型设计模式,用于将一个复杂对象的构建过程与其表示分离,以便可以使用相同的构建过程创建不同的表示。 Builder(建造者)接口或抽象类 : 定义了构建对象的各个步骤的方法。 ConcreteBuilder(具体建造者)类 : 实现了 Builder 接口或继

    2024年04月15日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包