在平时开发中,Builder方式采用的流式对象创建,我们经常用到,比如Guava的CacheBuilder,如:
CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterAccess(2, TimeUnit.MINUTES)
.expireAfterWrite(10, TimeUnit.MINUTES)
.initialCapacity(100)
.build();又如Curator中的:
CuratorFramework curatorFramework = CuratorFrameworkFactory.
builder().
connectString(server.getConnectString()).
sessionTimeoutMs(1000).
retryPolicy(new RetryNTimes(3, 1000)).
build();本文主要来探讨一下,有没有一种通用的写法能够完成。其实,目前主要可以采用2种方式来实现:
根据Lombok官网https://projectlombok.org/的介绍如下:
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官网的描述可以看出:
Project Lombok是一个java库,其可以自动插入到你的编辑器和构建工具中,
使java代码更加生动。
使用Lombok提供的注解将会带来诸多改变,如:
-- 不需要再写getter、equals等方法
-- 一个注解将为类提供功能齐全的Builder,后续我们将会演示@Builder注解
-- 自动插入日志变量等等针对本文,我们只要使用Lombok的@Builder注解, 即可支持Builder构建对象。
其采用的是修改语法树的方式为标注@Builder注解的类,生成内部类完成。更多的Lombok知识,可以参考之前的文章《Lombok,简化代码的神器,你值得拥有》
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;
} 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);参考《https://blog.csdn.net/weixin_43935907/article/details/105003719》
思想
1、通过Supplier懒加载的特性保存需要创建的对象
2、将每一步的操作保存到Consumer中,存入到List集合
3、在build方法中通过Supplier的get()方法获取实例,
在遍历list调用每个Consumer的accept方法给该实例的参数进行初始化
定义一个可被Build的接口Buildable
import java.io.Serializable;
public interface Buildable<T> extends Serializable {
T build();
}
通用Builder写法
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);
}
}测试一下
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;
}
}
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));
}
}结果输出
{
"authors":[
"Eric"
],
"isbn":"1234567890",
"name":"Hello Java"
}
这样,一个通用的Builder构建器就完成了。
注意:在上述的示例中,我们赋值的方式只支持一个参数,因为定义的是:
/**
* 1个参数 Consumer
*/
@FunctionalInterface
public interface Consumer1<T, P1> {
void accept(T t, P1 p1);
}假设Book类中,还有一个init方法,用于给name和isbn参数赋值的话,是不支持的,如:
public void init(String name, String isbn) {
this.isbn = isbn;
this.name = name;
}如果想支持这种2个参数的情况,需要在Builder类中,补充如下内容:
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方法也可以支持,如:
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来完成,你的选择呢?