java中的builder模式

來自《effective+java中文版》:

靜態工廠和構造器有一個共同的侷限性,都不能很好的擴展到大量的可選參數。

重疊構造器模式

如果我們有一個類,表示一個用戶的個人信息,有些值是必須的,有些不是必須的。

必須的參數:用戶名、年齡、性別

非必須的參數:身高、住址、體重

public class User {

	private String username;
	
	private int age;
	
	private int sex;
	
	private float height;
	
	private String address;
	
	private float weight;
	
	public User(String username,int age,int sex){
		this(username,age,sex,0);
	}

	public User(String username,int age,int sex,float height){
		this(username,age,sex,height,"");
	}

	public User(String username,int age,int sex,float height,String address){
		this(username,age,sex,height,address,0f);
	}
	public User(String username,int age,int sex,float height,String address,float weight){
		this.username = username;
		this.age = age;
		this.sex = sex;
		this.height = height;
		this.address = address;
		this.weight = weight;
	}
}
當你想要創建實例的時候,利用參數列表最短的構造器,該列表包含了要設置的參數:

User u = new User("china", 23, 1, 55.5f, "",179.2f);
這個構造器調用通常許多你不想設置的參數,但還是不得不爲他們傳值。隨着屬性列表的增多,很快就會失去控制。

重疊構造器模式可行,但是當有許多參數的時候,客戶端代碼會很難寫,並且難以閱讀

如果讀者想知道某一個參數是什麼意思,還需要仔細的數着參數個數來嘆究竟。可能會導致微妙的錯誤,很可能一不小心兩個參數位置顛倒了,微妙的錯誤也可能影響極大的後患。

遇到許多構造器參數的時候,還有一種javabbeans模式,這種模式只需要一個午餐的構造方法,然後通過setter方法給每個必要的參數和不必要的參數進行賦值:

public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public int getSex() {
		return sex;
	}

	public void setSex(int sex) {
		this.sex = sex;
	}

	public float getHeight() {
		return height;
	}

	public void setHeight(float height) {
		this.height = height;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public float getWeight() {
		return weight;
	}

	public void setWeight(float weight) {
		this.weight = weight;
	}
	

這種模式彌補了重疊構造器模式的不足,創建實例容易,代碼也易讀。

但是,javabeans模式本身有着很嚴重的缺點,因爲構造過程被分到幾個調用中,構造過程中可能出現javabean不一致的狀態,類無法僅僅通過構造器參數的有效性來保證一致性,試圖使用處於不一致的狀態,將會導致失敗。javabean模式阻止了把類做成不可變的可能。程序員需要通過額外的努力來保證線程的安全。

幸運的是,還有第三種方法,既能保證像重疊器模式那樣的安全性,也能保證javabeans模式那樣的可讀性,這就是Builder模式的一種形式。不直接生成想要的對象,而是讓客戶端利用所有必要的參數調用構造器,得到一個builder對象,然後客戶端在builder對象上調用類似setter的方法,來設置每個相關的可選參數。最後客戶端調用無參的builder方法生成不可變的對象。實例:

public class User {

	private String username;

	private int age;

	private int sex;

	private float height;

	private String address;

	private float weight;

	public User(Builder builder) {
		username = builder.username;
		age = builder.age;
		sex = builder.sex;
		height = builder.height;
		address = builder.address;
		weight = builder.weight;
	}

	public static class Builder {
		// required
		private String username = "";
		private int age = 0;
		private int sex = 1;
		// optional
		private float height = 0.0f;
		private String address = "";
		private float weight = 0.0f;

		public Builder(String username, int age, int sex) {
			this.username = username;
			this.age = age;
			this.sex = sex;
		}

		public Builder height(float val) {
			height = val;
			return this;
		}

		public Builder address(String val) {
			address = val;
			return this;
		}

		public Builder weight(float val) {
			weight = val;
			return this;
		}

		public User build() {
			return new User(this);
		}
	}

	public String getUsername() {
		return username;
	}

	public int getAge() {
		return age;
	}

	public int getSex() {
		return sex;
	}

	public float getHeight() {
		return height;
	}

	public String getAddress() {
		return address;
	}

	public float getWeight() {
		return weight;
	}

}

調用代碼:

public class TestBuilder {

	public static void main(String[] args) {
		User user = new User.Builder("china",23,1).address("hangzhou").build();
		System.out.println(user.getUsername()+","+user.getAge()+","+user.getSex()+","+user.getAddress());
	}
}

打印出:

china,23,1,hangzhou。

同樣,還可以設置更多參數:

User user = new User.Builder("china",23,1).address("hangzhou").height(1.78f).build();

user.getHeight()可以獲取到:1.78

這樣客戶端代碼更容易編寫,更易於閱讀。

發佈了33 篇原創文章 · 獲贊 1 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章