Builder设计模式
Buider设计模式作为23种设计模式之一,其定义为:讲一个复杂对象的构建与它的表示相分离,使得同样的构建过程可以创建不同的表示。
通常情况下,Builder模式中有以下角色:
- Builder(建造者)
Builder角色负责定义用于生成实例的接口,因此Builder类一般为抽象类,其中的方法为抽象方法 - ConcreteBuilder(具体的建造者)
ConcreteBuilder角色是负责实现Buider角色所定义的抽象方法的类,通常定义了生成实例被调用时所执行的具体操作。此外,在该角色中通常还定义了获取最终生成结果的方法。 - Director(监工)
Director角色负责使用Builder角色的接口来生成实例,也就是说它调用的Builder角色中的抽象方法,因此它并不依赖于ConcreteBuilder角色,这样也有利于降低耦合性和增加复用性。
下面用代码具体演示一下。
- 首先创建Builder角色。
public abstract class Builder {
abstract void buildPart1();
abstract void buildPart2();
abstract void buildPart3();
abstract void buildPart4();
}
- 创建ConcreteBuilder角色
public class ConcreteBuilder1 extends Builder {
private ProductBean bean = new ProductBean();
@Override
void buildPart1() {
bean.setPart1("石头");
}
@Override
void buildPart2() {
bean.setPart2("水泥");
}
@Override
void buildPart3() {
bean.setPart3("塑料");
}
@Override
void buildPart4() {
bean.setPart4("铁");
}
public ProductBean getResult() {
return bean;
}
}
public class ConcreteBuilder2 extends Builder {
private ProductBean bean = new ProductBean();
@Override
void buildPart1() {
bean.setPart1("水晶");
}
@Override
void buildPart2() {
bean.setPart2("活性炭");
}
@Override
void buildPart3() {
bean.setPart3("木头");
}
@Override
void buildPart4() {
bean.setPart4("铜");
}
public ProductBean getResult() {
return bean;
}
}
其中ProductBean为具体需要构建的对象
public class ProductBean {
private String part1;
private String part2;
private String part3;
private String part4;
public String getPart1() {
return part1;
}
public void setPart1(String part1) {
this.part1 = part1;
}
public String getPart2() {
return part2;
}
public void setPart2(String part2) {
this.part2 = part2;
}
public String getPart3() {
return part3;
}
public void setPart3(String part3) {
this.part3 = part3;
}
public String getPart4() {
return part4;
}
public void setPart4(String part4) {
this.part4 = part4;
}
@Override
public String toString() {
return "ProductBean{" +
"part1='" + part1 + '\'' +
", part2='" + part2 + '\'' +
", part3='" + part3 + '\'' +
", part4='" + part4 + '\'' +
'}';
}
}
- 创建Director角色
public class Director {
private Builder builder;
public Director(Builder builder){
this.builder = builder;
}
public void construct(){
builder.buildPart1();
builder.buildPart2();
builder.buildPart3();
builder.buildPart4();
}
}
截止到现在,Builder设计模式中所需要的角色都已经创建完成,接下来我们在测试类中进行一下测试:
public class Test {
public static void main(String args[]){
ConcreteBuilder1 concreteBuilder1 = new ConcreteBuilder1();
Director director = new Director(concreteBuilder1);
director.construct();
System.out.println("ConcreteBuilder1: "+ concreteBuilder1.getResult().toString() );
ConcreteBuilder2 concreteBuilder2 = new ConcreteBuilder2();
Director director1 = new Director(concreteBuilder2);
director1.construct();
System.out.println("ConcreteBuilder2: " + concreteBuilder2.getResult().toString() );
}
}
执行上面的测试代码,可以看到控制台输出的结果为:
ConcreteBuilder1: ProductBean{part1=‘石头’, part2=‘水泥’, part3=‘塑料’, part4=‘铁’}
ConcreteBuilder2: ProductBean{part1=‘水晶’, part2=‘活性炭’, part3=‘木头’, part4=‘铜’}
由此我们可以看到,由于具体的ConcreteBuilder的实现方式不同,同样的构建过程创建了两种不同的表示,调用者在构建具体的对象时只需要创建对应的ConcreteBuilder角色并作为参数传递给Director角色,然后调用Director角色的construct()方法即可,并不需要关注具体的构建过程。并且由于Director角色并不依赖于ConcreteBuilder角色,即使具体的构建过程改变也只需要增加新的ConcreteBuilder角色,并不会影响Director角色的复用性。但是经过多年的实践演化,在Java&Android开发中广泛使用的是它的一个变种。作为一个Android开发人员,最熟悉的莫过于AlertDialog的使用,语句如下:
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle("标题")
.setMessage("对话框内容")
.setIcon(R.drawable.ic_launcher)
.create();
dialog.show();
再比如网络请求框架OkHttp的请求封装:
private Request(Builder builder){
this.url = builder.url;
this.method = builder.method;
this.head = builder.head;
this.body = builder.body;
this.tag = builder.tag != null ? builder.tag : this ;
}
经典的Buider模式重点在于抽象出对象的创建步骤,并通过调用不同的具体实现从而得到不同的结果,而变种的Buider模式的目的在于减少对象创建过程中韵如的多个重载构造函数、可选参数以及setters过度使用导致的不必要的复杂性。下面我们通过一个简单的User对象的创建过程为例子进行说明:
public class User {
private final String firstName; //必选
private final String lastName; //必选
private final String gender; //可选
private final int age; //可选
private final String phone; //可选
}
从代码中我们可以看出User类有五个属性,其中firstName和lastName为必选的也就是必须要初始化的,而其他三个属性为可选属性,由于都是不可变的(final),所以必须要在构造函数中对这些属性进行初始化,那么我们为了满足所有情况就必须创建多个构造函数:
public class User {
private final String firstName; //必选
private final String lastName; //必选
private final String gender; //可选
private final int age; //可选
private final String phone; //可选
public User(String firstName, String lastName) {
this(firstName,lastName,"");
}
public User(String firstName, String lastName, String gender) {
this(firstName,lastName,gender,0);
}
public User(String firstName, String lastName, String gender, int age) {
this(firstName,lastName,gender,age,"");
}
public User(String firstName, String lastName, String gender, int age, String phone) {
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
this.age = age;
this.phone = phone;
}
}
这种构造函数的方式虽然简单,但只适用于只有少量几个属性的情况,随着User类属性的增加,代码将变得越来越难以阅读和维护。更严重的是对于类的使用者而言,代码将变得更加难以使用。
另外一种可先方案是遵循JavaBean的规范,定义一个默认的无参构造函数,并为类的每个属性定义getter和setter方法:
public class User {
private String firstName; //必选
private String lastName; //必选
private String gender; //可选
private int age; //可选
private String phone; //可选
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
这种方案的好处是易于阅读和维护,使用者可以创建一个空实例,并只设置感兴趣的属性值,但是这个方案存在两个缺点:
- User类是可变的,从而失去了不可变对象的很多好处(编程常识之一:我们应该尽量将属性值定义为不可变的)
- User类的实例状态不连续,如果你想创建一个同时具有五个属性值的类实例,那么直到第五个属性值的set函数被调用时,该实例才具有完整连续的状态,这就意味着实例的调用者可能会看到该实例的不连续状态。为了同时兼顾前两种方案的优势,变种的Builder模式应运而生:
public class User {
private final String firstName; //必选
private final String lastName; //必选
private final String gender; //可选
private final int age; //可选
private final String phone; //可选
private User(Builder builder) {
firstName = builder.firstName;
lastName = builder.lastName;
gender = builder.gender;
age = builder.age;
phone = builder.phone;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getGender() {
return gender;
}
public int getAge() {
return age;
}
public String getPhone() {
return phone;
}
public static final class Builder {
private final String firstName;
private final String lastName;
private String gender;
private int age;
private String phone;
public Builder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Builder gender(String gender) {
this.gender = gender;
return this;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public User build() {
return new User(this);
}
}
}
从上面的代码可以看出以下几点:
- User类的构造函数是私有的,这就意味着调用者不能直接实例化这个类
- User类是不可变的
- UserBuilder的构造函数只接收必选的属性值作为参数,并且只有必选的属性值设置为final,以此保证它们在构造函数中初始化
下面我们在测试类中进行测试:
public class Test {
public static void main(String args[]){
User user = new User.Builder("san","zhang")
.gender("man")
.age(23)
.phone("18800000000")
.build();
System.out.print(user.toString());
}
}
控制台输出 User{firsaName=‘san’, lastName=‘zhang’, gender=‘man’, age=23, phone=‘18800000000’}