首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >一个通用的 Builder构建器写法

一个通用的 Builder构建器写法

作者头像
孟君
发布2022-11-21 20:31:51
发布2022-11-21 20:31:51
5430
举报

在平时开发中,Builder方式采用的流式对象创建,我们经常用到,比如Guava的CacheBuilder,如:

代码语言:javascript
复制
  CacheBuilder.newBuilder()
          .maximumSize(10000)
          .expireAfterAccess(2, TimeUnit.MINUTES)
          .expireAfterWrite(10, TimeUnit.MINUTES)
          .initialCapacity(100)
          .build();

又如Curator中的:

代码语言:javascript
复制
 CuratorFramework curatorFramework = CuratorFrameworkFactory.
                builder().
                connectString(server.getConnectString()).
                sessionTimeoutMs(1000).
                retryPolicy(new RetryNTimes(3, 1000)).
                build();

本文主要来探讨一下,有没有一种通用的写法能够完成。其实,目前主要可以采用2种方式来实现:

  • 使用Lombok神器
  • 使用JDK1.8后的Supplier

使用Lombok神器

根据Lombok官网https://projectlombok.org/的介绍如下:

代码语言:javascript
复制
Project Lombok is a java library that automatically plugs into 
your editor and build tools, spicing up your java.
Never write another getter or equals method again, 
with one annotation your class has a fully featured builder, 
Automate your logging variables, and much more.

根据Lombok官网的描述可以看出:

代码语言:javascript
复制
Project Lombok是一个java库,其可以自动插入到你的编辑器和构建工具中,
使java代码更加生动。
使用Lombok提供的注解将会带来诸多改变,如:
 -- 不需要再写getter、equals等方法
 -- 一个注解将为类提供功能齐全的Builder,后续我们将会演示@Builder注解
 -- 自动插入日志变量等等

针对本文,我们只要使用Lombok的@Builder注解, 即可支持Builder构建对象。

其采用的是修改语法树的方式为标注@Builder注解的类,生成内部类完成。更多的Lombok知识,可以参考之前的文章《Lombok,简化代码的神器,你值得拥有

  • Book类
代码语言:javascript
复制
import java.util.List;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;


@Getter
@Setter
@Builder
public class Book {

  private String name;

  private String isbn;

  private List<String> authors;

}
  • 类创建方法
代码语言:javascript
复制
    Book book = Book.builder().name("Hello java")
              .isbn("1234567890").authors(Arrays.asList("Eric"))
              .build();
    // Book [name=Hello java, isbn=1234567890, authors=[Eric]]
    System.out.println(book);

使用JDK1.8后的Supplier

参考《https://blog.csdn.net/weixin_43935907/article/details/105003719》

思想

代码语言:javascript
复制
1、通过Supplier懒加载的特性保存需要创建的对象
2、将每一步的操作保存到Consumer中,存入到List集合
3、在build方法中通过Supplier的get()方法获取实例,
在遍历list调用每个Consumer的accept方法给该实例的参数进行初始化

定义一个可被Build的接口Buildable

代码语言:javascript
复制
import java.io.Serializable;

public interface Buildable<T> extends Serializable  {

  T build();
}

通用Builder写法

代码语言:javascript
复制

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * 通用的 Builder 模式构建器
 */
public class Builder<T> implements Buildable<T> {

  private static final long serialVersionUID = -1984695100816478391L;

  private final Supplier<T> instantiator;

  private List<Consumer<T>> modifiers = new ArrayList<>();

  public Builder(Supplier<T> instantiator) {
    this.instantiator = instantiator;
  }

  public static <T> Builder<T> of(Supplier<T> instantiator) {
    return new Builder<>(instantiator);
  }

  public <P1> Builder<T> with(Consumer1<T, P1> consumer, P1 p1) {
    Consumer<T> c = instance -> consumer.accept(instance, p1);
    modifiers.add(c);
    return this;
  }


  @Override
  public T build() {
    T value = instantiator.get();
    modifiers.forEach(modifier -> modifier.accept(value));
    modifiers.clear();
    return value;
  }

  /**
   * 1个 参数 Consumer
   */
  @FunctionalInterface
  public interface Consumer1<T, P1> {
    void accept(T t, P1 p1);
  }

}

测试一下

代码语言:javascript
复制
import java.io.Serializable;
import java.util.List;

public class Book implements Serializable {

  private static final long serialVersionUID = 9029811615932090773L;

  private String name;

  private String isbn;

  private List<String> authors;

  public Book() {
  }

  public Book(String name) {
    super();
    this.name = name;
  }

  /**
   * @return the name
   */
  public String getName() {
    return name;
  }

  /**
   * @param name the name to set
   */
  public void setName(String name) {
    this.name = name;
  }

  /**
   * @return the isbn
   */
  public String getIsbn() {
    return isbn;
  }

  /**
   * @param isbn the isbn to set
   */
  public void setIsbn(String isbn) {
    this.isbn = isbn;
  }

  /**
   * @return the authors
   */
  public List<String> getAuthors() {
    return authors;
  }

  /**
   * @param authors the authors to set
   */
  public void setAuthors(List<String> authors) {
    this.authors = authors;
  }

}
代码语言:javascript
复制
import java.util.Arrays;

import com.alibaba.fastjson.JSON;

public class BookExample {

  public static void main(String[] args) {
    Book book = Builder.of(Book::new)
        .with(Book::setName, "Hello Java")
        .with(Book::setIsbn, "1234567890")
        .with(Book::setAuthors, Arrays.asList("Eric"))
        .build();
    System.out.println(JSON.toJSONString(book, true));

  }

}

结果输出

代码语言:javascript
复制
{
  "authors":[
    "Eric"
  ],
  "isbn":"1234567890",
  "name":"Hello Java"
}

这样,一个通用的Builder构建器就完成了。

注意:在上述的示例中,我们赋值的方式只支持一个参数,因为定义的是:

代码语言:javascript
复制

  /**
   * 1个参数 Consumer
   */
  @FunctionalInterface
  public interface Consumer1<T, P1> {
    void accept(T t, P1 p1);
  }

假设Book类中,还有一个init方法,用于给name和isbn参数赋值的话,是不支持的,如:

代码语言:javascript
复制
  public void init(String name, String isbn) {
    this.isbn = isbn;
    this.name = name;
  }

如果想支持这种2个参数的情况,需要在Builder类中,补充如下内容:

代码语言:javascript
复制

  public <P1, P2> Builder<T> with(Consumer2<T, P1, P2> consumer, P1 p1, P2 p2) {
    Consumer<T> c = instance -> consumer.accept(instance, p1, p2);
    modifiers.add(c);
    return this;
  }


  /**
   * 2个 参数 Consumer
   */
  @FunctionalInterface
  public interface Consumer2<T, P1, P2> {
    /**
     * 接收参数方法
     * 
     * @param t  对象
     * @param p1 参数一
     * @param p2 参数二
     */
    void accept(T t, P1 p1, P2 p2);
  }

这样,init方法也可以支持,如:

代码语言:javascript
复制
import java.util.Arrays;

import com.alibaba.fastjson.JSON;

public class BookExample {

  public static void main(String[] args) {
    Book book = Builder.of(Book::new)
        .with(Book::init, "Hello Java111111", "123456789011111111111")
        .with(Book::setAuthors, Arrays.asList("Eric, Joan")).build();
    System.out.println(JSON.toJSONString(book, true));

  }

}

所以,如果想支持更多的参数,如3个、4个,则可以采用类似上述2个参数操作,补充Consumer3、Consumer4等实现即可。

至此,我们一个通用的Builder构建器写法就完成了。

在可以使用Lombok的项目中,我更倾向于使用Lombok来完成,你的选择呢?

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 孟君的编程札记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用Lombok神器
  • 使用JDK1.8后的Supplier
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档