1 定义
建造者模式(Builder Pattern)属于创建型设计模式。又有人称为创建者模式、生成器模式、构造器模式等,它主要用于复杂对象的创建。那什么是复杂对象呢?其实简单地说就是类中构造方法有多个重载的版本和最终构造方法参数特别多的类的对象或者构造方法少但是参数特别多的类的对象。因为这种类需要外部传入相当多的参数来决定类的最终表现,所以它是复杂的。使用者在创建对象时需要决定使用哪个构造方法,然而里面的参数相当多还得搞清楚这些参数的含义,或者构造方法简单但需要多行代码来设置相当多的属性才能完成最终想要的对象效果。这种复杂的类让使用者使用起来相当麻烦。然而建告者模式就是为了解决这类情况下的痛点而出现的。
2 实现方式
假设当前有一个用户类User,它内部拥有两个必须字段mFirstName和mLastName,以及三个可选字段mSex、mAge和mTelephone。如果按常规思路实现该类可以有折叠构造函数模式或者JavaBean模式。
2.1 折叠构造函数方式
public class User {
private String mFirstName; // 必须
private String mLastName; // 必须
private boolean mSex; // 可选
private int mAge; // 可选
private String mTelephone; // 可选
public User(String firstName, String lastName) {
this(firstName, lastName, true, 0, null);
}
public User(String firstName, String lastName, boolean sex) {
this(firstName, lastName, sex, 0, null);
}
public User(String firstName, String lastName, int age) {
this(firstName, lastName, true, age, null);
}
public User(String firstName, String lastName, String telephone) {
this(firstName, lastName, true, 0, telephone);
}
public User(String firstName, String lastName, boolean sex, int age) {
this(firstName, lastName, sex, age, null);
}
public User(String firstName, String lastName, boolean sex, String telephone) {
this(firstName, lastName, sex, 0, telephone);
}
public User(String firstName, String lastName, int age, String telephone) {
this(firstName, lastName, true, age, telephone);
}
public User(String firstName, String lastName, boolean sex, int age, String telephone) {
mFirstName = firstName;
mLastName = lastName;
mSex = sex;
mAge = age;
mTelephone = telephone;
}
}
该类因为需要提供外部根据不同的参数达到不同的表现,所以定义了8个构造方法,其中可见每个构造方法中都有firstName和lastName字段,表示为实例化时必须字段,从第2到第7个构造方法分别对应不同的参数组合出不一样的表现,最终前面7构造方法会汇集到第8个构造方法中来完成字段的赋值。
所以在多个构造方法提供下,使用者在创建对象时需要决定使用哪个构造方法,然而里面的参数相当多还得搞清楚这些参数的含义。如果增加字段的话,就要增加对应的构造方法,代码将变的难以理解和维护。
2.2 JavaBean方式
public class User {
private final String mFirstName; // 必须
private final String mLastName; // 必须
private boolean mSex; // 可选
private int mAge; // 可选
private String mTelephone; // 可选
public User(String firstName, String lastName) {
mFirstName = firstName;
mLastName = lastName;
}
public void setSex(boolean sex) {
mSex = sex;
}
public void setAge(int age) {
mAge = age;
}
public void setTelephone(String telephone) {
mTelephone = telephone;
}
}
该类可选的字段通过set属性的形式进行赋值,从而仅实现一个必须参数构造函数,但是这样做使得调用方需要使用多行代码才能创建出一个自己满意的对象,在调用setXXX方法前,对象处于不完整状态,而且也丧失了不可变对象的所有好处。
2.3 简化版的建造者模式
public class User {
private final String mFirstName; // 必须
private final String mLastName; // 必须
private final boolean mSex; // 可选
private final int mAge; // 可选
private final String mTelephone; // 可选
private User(Builder builder) {
mFirstName = builder.firstName;
mLastName = builder.lastName;
mSex = builder.sex;
mAge = builder.age;
mTelephone = builder.telephone;
}
public static class Builder {
private final String firstName;
private final String lastName;
private boolean sex;
private int age;
private String telephone;
public Builder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Builder sex(boolean sex) {
this.sex = sex;
return this;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Builder telephone(String telephone) {
this.telephone = telephone;
return this;
}
public User build() {
return new User(this);
}
}
}
客户端:
User user = new User.Builder("子", "云心")
.sex(true)
.age(18)
.telephone("1234567890")
.build();
建造者模式需要类内部创建一个静态内部类,如上述中的Builder,Builder中有一套跟User类对应的字段,而每个可选字段又对应一个同名的方法,这些方法都返回类本身类型Builder,最终在build方法中将自己this传入给User的私有构造方法来new出一个User对象并返回。User构造方法接收Builder对象,再将Builder的每一个字段赋值给本身每一个final的字段来完成初始化。
2.3.1 小结
简化版的建造者模式一般要建造的类的构造方法是私有的,这保证了调用者不可随意直接创建实例,类中字段使用final修饰表示其不可变性。调用者使用流式句语一步一步地完成对象的创建可使代码更易读和易维护,另外无论什么时候创建出来的对象都是保持不变和状态完整的。一般地当一个类的构造方法参数超过4个,而且参数存在可选参数时,就可以考虑使用建造者模式。
2.4 传统版的建造者模式
上面建造者模式是在Java中常见的一种简化版的建造者模式,传统版的建造者模式与其有一定的不同和增加了复杂度。传统版的建造者模式包括4个角色,分别是:
- 产品(Product):要生成的对象,即目标的复杂对象类,也就是上面示例中的User。
- 抽象建造者(Builder):用于定义了构建Product的抽象步骤,可以是抽象类或接口。
- 具体建造者(ConcreteBuilder):Builder的实现类。
- 指挥者(Director):指挥并构造一个使用Builder的对象。
产品类,使用了JavaBean的方式来定义:
public class User {
private final String mFirstName; // 必须
private final String mLastName; // 必须
private boolean mSex; // 可选
private int mAge; // 可选
private String mTelephone; // 可选
public User(String firstName, String lastName) {
mFirstName = firstName;
mLastName = lastName;
}
public void setSex(boolean sex) { mSex = sex; }
public void setAge(int age) { mAge = age; }
public void setTelephone(String telephone) { mTelephone = telephone; }
}
抽象建造者类和具体实现建造者类,对应着具体产品类中的set属性定义出抽象方法和实现,在具体实现类中完成产品类的创建:
public abstract class UserBuilder {
public abstract void setSex();
public abstract void setAge();
public abstract void setTelephone();
public abstract User getUser();
}
public class ManUserBuidler extends UserBuilder {
private User mUser;
public ManUserBuidler(String firstName, String lastName) {
mUser = new User(firstName, lastName);
}
@Override
public void setSex() {
mUser.setSex(true);
}
@Override
public void setAge() {
mUser.setAge(18);
}
@Override
public void setTelephone() {
mUser.setTelephone("1234567890");
}
@Override
public User getUser() {
return mUser;
}
}
指挥者类,主要作用于隔离生产过程和调用者,而且负责控制产品的生产过程,它针对抽象建造者进行完整的属性设置从而达到对象的完整性。
public class UserDirector {
public void makeUser(UserBuilder builder) {
builder.setSex();
builder.setAge();
builder.setTelephone();
}
}
客户端:
UserDirector director = new UserDirector();
UserBuilder builder = new ManUserBuidler("子", "云心");
director.makeUser(builder);
User user = builder.getUser();
2.4.1 小结
传统版的建造者模式会事先通过具体实现建造者的角色完成了产品的建造,如果需要不同的产品表现需要另外再定义一个新的具体建造者。好处在于建造代码与表现相分离,调用者只需要指定要建造的类再通过指挥者就可以得到它们,而不需要关心具体建造过程和细节。也可以跟产品生产过程进行了隔离。不过缺点也较为明显,当产品需要新增字段时,那么修改的地方较多。所以它适用于创建较为稳定不会发生变化的复杂对象,在Java开发中一般建议使用简化版的建造者模式。