Java高效程式設計之Builder模式
前言
在《Effective Java 第2版》中有提到,遇到多個構造器引數時要考慮使用構建器(Builder模式)。相比於重疊構造器(telescoping constructor)模式和JavaBeans模式,Builder模式實現的物件更利於使用。
下面從一個Person例子進行分析以上三種設計模式的使用,Person類有兩個必要引數(id和name),有5個可選引數(age,sex,phone,address和desc)
1 重疊構造器模式
我們先來看看程式設計師一向習慣使用的重疊構造器模式,在這種模式下,你提供第一個只有必要引數的構造器,第二個構造器有一個可選引數,第三個有兩個可選引數,依此類推,最後一個構造器包含所有的可選引數。下面看看其程式設計實現:
/**
* 使用重疊構造器模式
*/
public class Person {
//必要引數
private final int id;
private final String name;
//可選引數
private final int age;
private final String sex;
private final String phone;
private final String address;
private final String desc;
public Person(int id, String name) {
this (id, name, 0);
}
public Person(int id, String name, int age) {
this(id, name, age, "");
}
public Person(int id, String name, int age, String sex) {
this(id, name, age, sex, "");
}
public Person(int id, String name, int age, String sex, String phone) {
this (id, name, age, sex, phone, "");
}
public Person(int id, String name, int age, String sex, String phone, String address) {
this(id, name, age, sex, phone, address, "");
}
public Person(int id, String name, int age, String sex, String phone, String address, String desc) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
this.phone = phone;
this.address = address;
this.desc = desc;
}
}
從上面的程式碼中,當你想要建立例項的時候,就利用引數列表最短的構造器,但該列表中包含了要設定的所有引數:
Person person = new Persion(1, "李四", 20, "男", "18800000000", "China", "測試使用重疊構造器模式");
這個構造器呼叫通常需要許多你本不想設定的引數,但還是不得不為它們傳遞值。
一句話:重疊構造器可行,但是當有許多引數的時候,建立使用程式碼會很難寫,並且較難以閱讀。
2 JavaBeans模式
遇到許多構造器引數的時候,還有第二種代替辦法,即JavaBeans模式。在這種模式下,呼叫一個無參構造器來建立物件,然後呼叫setter辦法來設定每個必要的引數,以及每個相關的可選引數:
/**
* 使用JavaBeans模式
*/
public class Person {
//必要引數
private int id;
private String name;
//可選引數
private int age;
private String sex;
private String phone;
private String address;
private String desc;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSex(String sex) {
this.sex = sex;
}
public void setPhone(String phone) {
this.phone = phone;
}
public void setAddress(String address) {
this.address = address;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
這種模式彌補了重疊構造器模式的不足。建立例項很容易,這樣產生的程式碼讀起來也很容易:
Person person = new Person();
person.setId(1);
person.setName("李四");
person.setAge(20);
person.setSex("男");
person.setPhone("18800000000");
person.setAddress("China");
person.setDesc("測試使用JavaBeans模式");
遺憾的是,JavaBeans模式自身有著很重要的缺點。因為構造過程被分到了幾個呼叫中,在構造過程中JavaBean可能處於不一致的狀態。類無法僅僅通過檢驗構造器引數的有效性來保證一致性。
3 Builder模式(推薦)
幸運的是,還有第三種替代方法,既能保證像重疊構造器模式那樣的安全性,也能保證像JavaBeans模式那麼好的可讀性。這就是Builder模式的一種形式,不直接生成想要的物件,而是讓客戶端利用所有必要的引數呼叫構造器(或者靜態工廠),得到一個builder物件。然後客戶端在builder物件上呼叫類似於setter的方法,來設定每個相關的可選引數。最後,客戶端呼叫無參的builder方法來生成不可變的物件。這個builder是它構建類的靜態成員類。下面就是它的示例:
/**
* 使用Builder模式
*/
public class Person {
//必要引數
private final int id;
private final String name;
//可選引數
private final int age;
private final String sex;
private final String phone;
private final String address;
private final String desc;
private Person(Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.age = builder.age;
this.sex = builder.sex;
this.phone = builder.phone;
this.address = builder.address;
this.desc = builder.desc;
}
public static class Builder {
//必要引數
private final int id;
private final String name;
//可選引數
private int age;
private String sex;
private String phone;
private String address;
private String desc;
public Builder(int id, String name) {
this.id = id;
this.name = name;
}
public Builder age(int val) {
this.age = val;
return this;
}
public Builder sex(String val) {
this.sex = val;
return this;
}
public Builder phone(String val) {
this.phone = val;
return this;
}
public Builder address(String val) {
this.address = val;
return this;
}
public Builder desc(String val) {
this.desc = val;
return this;
}
public Person build() {
return new Person(this);
}
}
}
注意Person是不可變得,所有的預設引數值都單獨放在一個地方。builder的setter方法返回builder本身。以便可以把連線起來。下面是客戶端使用程式碼:
/**
* 測試使用
*/
public class Test {
public static void main(String[] args) {
Person person = new Person.Builder(1, "張三")
.age(18).sex("男").desc("測試使用builder模式").build();
System.out.println(person.toString());
}
}
這樣的客戶端程式碼很容易編寫,更為重要的是,易於閱讀。
本文是參照《Effective Java 第2版》第二章建立和銷燬物件第2條(遇到)所做的學習記錄筆記,本書裡面還有很多非常不錯的Java高效程式設計方法,有興趣的可以找來看看。