在创建一个对象的时候,构造器参数有点多,而且有些参数还是可选的,再者还有不少同类型的,那就更应该使用 builder 模式了。
使用 Builder 模式的初衷是 把易变性(mutability)移动到Builder类,而使得要被创建的对象变得不可变(immutable)。
传统方式
创建静态内部类 Builder ,此 Builder 和外部类拥有一样的属性。包括一些返回 this对象的 setter方法,和一个 build 方法调用 外部类的 以builder对象为参数构造器 来创建 外部类对象。
@Getter
public class Blog {
private final String title;
private final String content;
private final String label;
private final String author;
public Blog(BlogBuilder builder){
this.title = builder.title;
this.content = builder.content;
this.label = builder.label;
this.author = builder.author;
}
static class BlogBuilder{
private String title;
private String content;
private String label;
private String author;
BlogBuilder title(String title){
this.title = title;
return this;
}
BlogBuilder content(String content){
this.content = content;
return this;
}
BlogBuilder label(String label){
this.label = label;
return this;
}
BlogBuilder author(String author){
this.author = author;
return this;
}
Blog build(){
return new Blog(this);
}
}
}
对象创建过程如下:
@Test
public void test(){
Blog blog = new Blog.BlogBuilder()
.author("dachuili")
.content("classic/generic/lombok builder pattern")
.label("design patterns")
.title("builder pattern")
.build();
assertSame(blog.getAuthor(), "dachuili");
assertSame(blog.getTitle(), "builder pattern");
assertSame(blog.getContent(), "classic/generic/lombok builder pattern");
assertSame(blog.getLabel(), "design patterns");
}
Lombok方式
Lombok提供了注解 @Builder ,一步实现 Builder 模式。不要忘了和@Value 一起用。
import lombok.Builder;
import lombok.Data;
import lombok.Value;
@Builder
@Value
@Data
public class Article {
private String title;
private String content;
private String label;
private String author;
}
创建过程:
@Test
public void test(){
Article article = Article.builder()
.author("dachuili")
.content("classic/generic/lombok builder pattern")
.label("design patterns")
.title("builder pattern")
.build();
assertSame(article.getAuthor(), "dachuili");
assertSame(article.getTitle(), "builder pattern");
assertSame(article.getContent(), "classic/generic/lombok builder pattern");
assertSame(article.getLabel(), "design patterns");
}
是不是很 easy!很 convenient!
@Builder 帮我们创建了 Builder 对象以及 这些返回 this对象的 setter方法。以下是lombok生成的ArticleBuilder对象 代码。
和传统方式并为区别。
@Generated
public static ArticleBuilder builder() {
return new ArticleBuilder();
}
@Generated
public static class ArticleBuilder {
@Generated
private String title;
@Generated
private String content;
@Generated
private String label;
@Generated
private String author;
@Generated
ArticleBuilder() {
}
@Generated
public ArticleBuilder title(String title) {
this.title = title;
return this;
}
@Generated
public ArticleBuilder content(String content) {
this.content = content;
return this;
}
@Generated
public ArticleBuilder label(String label) {
this.label = label;
return this;
}
@Generated
public ArticleBuilder author(String author) {
this.author = author;
return this;
}
@Generated
public Article build() {
return new Article(this.title, this.content, this.label, this.author);
}
}
通用的Builder
借助于 Java 8 提供的 Supplier和 BiConsumer 创建一个通用工具类,好玩是好玩,强行捏了一个builder出来,感觉违背了 builder模式的初衷,回到了 JavaBeans Pattern 那种setter方法。
public class GenericBuilder<T> {
private final Supplier<T> supplier;
private GenericBuilder(Supplier<T> supplier) {
this.supplier = supplier;
}
public static <T> GenericBuilder<T> of(Supplier<T> supplier) {
return new GenericBuilder<>(supplier);
}
public <P> GenericBuilder<T> with(BiConsumer<T, P> consumer, P value) {
return new GenericBuilder<>(() -> {
T object = supplier.get();
consumer.accept(object, value);
return object;
});
}
public T build() {
return supplier.get();
}
}
Post post = GenericBuilder.of(Post::new)
.with(Post::setTitle, "builder pattern")
.with(Post::setAuthor, "dachuili")
.with(Post::setLabel, "design patterns")
.with(Post::setContent, "classic/generic/lombok builder pattern")
.build();
Builder模式与抽象类
以下代码来自 Joshua Bloch 的《Effective Java》一书,抽象类有自己的Builder方法, 实现类有自己的 Builder方法。
首先是我们的抽象类 Pizza,定义了一些 topping,也就是Pizza上铺的食材(火腿、蘑菇、洋葱、辣椒、香肠之类的)。
public abstract class Pizza {
public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
final Set<Topping> toppings;
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone();
}
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self();
}
// Subclasses must override this method to return "this"
protected abstract T self();
abstract Pizza build();
}
}
其中一个实现类是 NyPizza 和 自己的 Builder 实现类,New York Pizza 有不同的size。
public class NyPizza extends Pizza{
public enum Size { SMALL, MEDIUM, LARGE }
private final Size size;
public static class Builder extends Pizza.Builder<Builder>{
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
@Override public NyPizza build() {
return new NyPizza(this);
}
@Override protected Builder self() { return this; }
}
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
}
另一个实现类是 Calzone,搜了下就是这个菜盒子。🤣
不区分大小,但有个属性 sauceInside 代表“是否内部有酱汁”。
public class Calzone extends Pizza{
private final boolean sauceInside;
public static class Builder extends Pizza.Builder<Builder>
{
private boolean sauceInside = false; // Default
public Builder sauceInside() {
sauceInside = true;
return this;
}
@Override public Calzone build() {
return new Calzone(this);
}
@Override protected Builder self() { return this; }
}
private Calzone(Builder builder) {
super(builder);
sauceInside = builder.sauceInside;
}
}
创建过程如下:
NyPizza pizza = new NyPizza.Builder(SMALL)
.addTopping(SAUSAGE).addTopping(ONION).build();
Calzone calzone = new Calzone.Builder()
.addTopping(HAM).sauceInside().build();