构建者模式
构建者模式(即 Builder 模式),该设计模式的主要目的是将一个复杂对象的构建和它的构造表示分离,使得对象的构建更加简单和可读。
构建者模式的结构一般如下图所示:
各个组件的介绍如下:
-
AbstractBuilder:一个为创建 Product 对象各个部件的抽象接口
-
ConcreateBuilder:该类会具体实现 AbstractBuilder,以达到构造和装配 Product 各个部件的功能
-
Director:一个使用 Builder 的接口对象
-
Product:具体的产品对象,当考虑到使用构建者模式来创建对象时,该对象的结构一般都会很复杂
在 Java 中的应用
一般每个初学 Java 的人都会知道使用 new
关键字来实例化一个对象,使用 new
关键字时,本质上是调用了对应的类的构造函数。对于简单的类来讲,一般属性字段不会特别多,
或者需要初始化的字段不是特别多,那么直接使用 new
关键字没有什么问题。
假设现在的有这样的一个类:Book,其中包含大量的属性,如下所示:
public class Book {
private long isbn; // 必需
private String name; // 必需
private double price; // 可选
private String author; // 可选
private String publisher; // 可选
private String des; // 可选
}
重叠构造器
如果希望这个类成为一个不可变类,那么就需要使用多个构造函数的方式来使得每个属性都能被初始化:
public class Book {
private final long isbn; // 必需
private final String name; // 必需
private final double price; // 可选
private final String author; // 可选
private final String publisher; // 可选
private final String des; // 可选
public Book(long isbn, String name) {
this(isbn, name, 0.0);
}
public Book(long isbn, String name, double price) {
this(isbn, name, price, null);
}
public Book(long isbn, String name, double price, String author) {
this(isbn, name, price, author, null);
}
public Book(long isbn, String name, double price, String author, String publisher) {
this(isbn, name, price, author, publisher, null);
}
public Book(long isbn, String name, double price, String author, String publisher, String des) {
this.isbn = isbn;
this.name = name;
this.price = price;
this.author = author;
this.publisher = publisher;
this.des = des;
}
}
这仅仅只是在有四个可选属性的情况下,需要新增如此多的构造函数,在一般的场景中,可选属性的数量远大于示例中的四个可选属性。这不仅会导致大量的冗余代码,而且会使得调用不正确的构造函数的概率提升
JavaBeans 模式
在大部分 Java 开发人员的学习过程中,都知道如何去读取数据库中的表记录。首先通过默认的无参构造函数实例化一个对象,然后通过这个对象的 setter
方法设置对应的属性。以上面的例子为例,通过 JavaBeans 的方式来设置属性如下:
public class Book {
private long isbn = -1L; // 必需
private String name = "xxx"; // 必需
private double price; // 可选
private String author; // 可选
private String publisher; // 可选
private String des; // 可选
public void setIsbn(long isbn) {this.isbn = isbn;}
public void setName(String name) {this.name = name;}
public void setPrice(double price) {this.price = price;}
public void setAuthor(String author) {this.author = author;}
public void setPublisher(String publisher) {this.publisher = publisher;}
public void setDes(String des) {this.des = des;}
}
当使用时,一般的使用方式如下:
Book book = new Book();
book.setIsbn(9780201563177L);
book.setAuthor("xhliu");
book.setDes("nothing");
book.setPublisher("Person");
book.setName("APUE");
这也是一般的使用方式,这种方式主要存在以下两个问题:
-
由于属性的设值放在多个
setter
方法中,在这个过程中对象可能会处于不一致的状态。如果是在多线程的环境中,这种方式可能会导致某些不可预见的问题(由于一般是在方法体中使用,因此不会出现这种问题) -
这种方式使得一个对象不再可能成为一个不可变对象,针对某些需求可能无法满足
构建者模式
在 《Effective Java》 中,作者对上面提到的两种方式存在的弊端提供了可行的解决方案—即构建者模式。调用方通过必要的参数调用构造器,得到一个 Builder 对象,然后再该 Builder 对象上调用 setter
方法来设置 Builder 对象的属性,最后通过 Builder 对象的 build()
方法将该对象转换为对应的产品对象。
以上面的例子为例,构建者模式来完成对象的创建:
public class Book {
private final long isbn; // 必需
private final String name ; // 必需
private final double price; // 可选
private final String author; // 可选
private final String publisher; // 可选
private final String des; // 可选
private Book(Builder builder) {
this.isbn = builder.isbn;
this.name = builder.name;
this.price = builder.price;
this.author = builder.author;
this.publisher = builder.publisher;
this.des = builder.des;
}
public static class Builder {
// 必需的参数
private long isbn;
private String name = "xxx";
// 可选参数
private double price;
private String author;
private String publisher;
private String des;
public Builder(long isbn, String name) {
this.isbn = isbn; this.name = name;
}
public Builder price(double price) {
this.price = price;
return this;
}
public Builder author(String author) {
this.author = author;
return this;
}
public Builder publisher(String publisher) {
this.publisher = publisher;
return this;
}
public Builder des(String des) {
this.des = des;
return this;
}
public Book build() {
return new Book(this);
}
}
}
在构造对象时,一般会按照如下的方式构造对象:
Book book = new Builder(9780201563177L, "APUE")
.author("Stevens").publisher("Person")
.price(106.5).des("")
.build();
可以看到,以构建者模式的方式来构造对象使得对象构造的过程更加可见、更加易于理解。同时可以看到,Book 对象此时是一个不可变对象,即构建者模式完美解决了上文两种构造方式中存在的弊端
类层次结构的构建者模式
和一般的 Java 类一样,构建者模式在面对继承关系时同样适用。适用平行层次的 Builder 对象时,各自嵌套在相应的类中。抽象类有抽象的 Builder,具体类有具体类的 Builder [2]
以 《Effective Java》中的披萨例子为例,用一个抽象类表示各色各样的披萨:
import java.util.EnumSet;
import java.util.Set;
public abstract class Pizza {
public enum Topping {HAM, MUSHROOM, ONION, PEPPER, SAUSAGE}
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>> {
// 创建一个空的指定类型的枚举集合
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(topping);
return self();
}
// 具体对象的构造过程
abstract Pizza build();
// 由子类重写该方法,返回当前对象
protected abstract T self();
}
Pizza(Builder<?> builder) {
// 使用保护性的clone 方法,使得产品对象最终是不可变对象
toppings = builder.toppings.clone();
}
}
现在有两种具体的披萨,一个是纽约风味的披萨,另一个是表示馅料内置的披萨。前者需要一个尺寸参数,而后者需要指定酱汁应该内置还是外置。
对于纽约风味的披萨:
public class NyPizza extends Pizza{
public enum Size {SMALL, MEDIUM, LARGE}
private final Size size;
public static class NyBuilder extends Pizza.Builder<NyBuilder> {
private final Size size;
public NyBuilder(Size size) {this.size = size;}
@Override
NyPizza build() {return new NyPizza(this);}
@Override
protected NyBuilder self() {return this;}
}
NyPizza(NyBuilder builder) {
super(builder);
this.size = builder.size;
}
}
对于馅料内置的披萨:
public class Calzone extends Pizza{
private final boolean sauceInside;
public static class CalBuilder extends Pizza.Builder<CalBuilder> {
private boolean sauceInside = false;
public CalBuilder sauceInside() {
sauceInside = true; return self();
}
@Override
Calzone build() {return new Calzone(this);}
@Override
protected CalBuilder self() {return this;}
}
Calzone(CalBuilder builder) {
super(builder);
this.sauceInside = builder.sauceInside;
}
}
当需要创建者两种类型的披萨时,可以类似下面这样做:
NyPizza nyPizza = new NyPizza.NyBuilder(NyPizza.Size.MEDIUM)
.addTopping(Topping.HAM).addTopping(Topping.MUSHROOM)
.build();
Calzone calzone = new Calzone.CalBuilder().sauceInside()
.addTopping(Topping.SAUSAGE).addTopping(Topping.PEPPER)
.build();
可以看到,使用构建者模式的方式来构造对象,会比一般的构造方式更加灵活、更加易懂。唯一的缺点在于需要编写额外的 Builder 类来完成构造的任务,因此性能会比传统构造对象的方式较差,但是这几乎可以忽略。对于复杂对象的构造场景(特别是可选参数特别多的时候),使用构建者模式来构造对象通常都是明智的选择。
参考:
[1] 《设计模式—可复用面向对象软件基础》
[2] 《Effective Java》(第三版)