模式的定義
將一個復(fù)雜對象的構(gòu)建與它的表示分離羹呵,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示骂际。
UML圖
Product 產(chǎn)品類 : 產(chǎn)品的抽象類。
Builder : 抽象類冈欢, 規(guī)范產(chǎn)品的組建歉铝,一般是由子類實現(xiàn)具體的組件過程。
ConcreteBuilder : 具體的構(gòu)建器.
Director : 統(tǒng)一組裝過程(可省略)凑耻。
應(yīng)用場景: 復(fù)雜對象的創(chuàng)建,內(nèi)部包含多個部件或者零件
優(yōu)點:使用靈活犯戏,易于擴展;不用關(guān)心內(nèi)部實現(xiàn)細節(jié)拳话,只注重結(jié)果
缺點:產(chǎn)生多余的Builder對象,消耗內(nèi)存种吸。
下面舉例來說明一下,畢竟例子是比較好的體現(xiàn)方式.
如果我們有一個類Student弃衍,他有很多的屬性,但是僅僅姓名和學號是必須賦值的坚俗,其他的屬性都是可選項镜盯,比如像下面代碼中所示.
public class Student {
private final int stuId;//必須
private final String name;//必須
private final int age;//可選
private final int gender;//可選
private final int address;//可選
...//還有很多可選屬性
}
那么我們怎么來創(chuàng)建一個Student對象呢岸裙?我們看到每個屬性都用final來修飾了,說明每個屬性都要在構(gòu)造方法中被初始化速缆,我們又必須提供各種參數(shù)數(shù)量的構(gòu)造方法降允,我們看如下代碼
public class Student {
private final int stuId;//必須
private final String name;//必須
private final int age;//可選
private final int gender;//可選
private final String address;//可選
public Student(int stuId,String name){
this(stuId,name,0,1,"");
}
public Student(int stuId,String name,int age){
this(stuId,name,age,1,"");
}
public Student(int stuId,String name,int age,int gender){
this(stuId,name,age,gender,"");
}
public Student(int stuId,String name,int age,int gender,String address){
this.stuId = stuId;
this.name = name;
this.age = age;
this.gender = gender;
this.address = address;
}
}
這樣做確實可以解決我們的需求,但這還是可選參數(shù)不多的情況艺糜,如果有很多可選參數(shù)剧董,我們就必須要寫很多個構(gòu)造函數(shù),這將導(dǎo)致代碼的可讀性和維護性變差破停,更重要的是翅楼,當我們要用到這個類的時候會感覺無從下手,我到底應(yīng)該用哪個構(gòu)造方法呢真慢?應(yīng)該用兩個參數(shù)的構(gòu)造方法還是用三個參數(shù)的呢毅臊?如果我用兩個參數(shù)的構(gòu)造方法,那么可選參數(shù)的默認值是多少黑界?
更棘手的是管嬉,如果我只想給Student對象設(shè)置address屬性而不設(shè)置age和gender屬性的話怎么辦?我們顯然還得再繼續(xù)添加構(gòu)造方法朗鸠,或者我們只能調(diào)用全參的構(gòu)造方法蚯撩,然后給age和gender屬性設(shè)置個默認值。
還有一點童社,我們看到stuId求厕,age,gender都是int類型的扰楼,那么我們在創(chuàng)建Student對象時呀癣,哪一個int類型的對象代表stuId,哪一個代表age弦赖,這還進一步增加了使用成本项栏。
那么我們還有沒有其他的辦法?答案是有蹬竖!我們可以只設(shè)置一個默認的無參構(gòu)造方法沼沈,然后給每個屬性添加getter和setter方法,代碼如下
public class Student {
private int stuId;//必須
private String name;//必須
private int age;//可選
private int gender;//可選
private String address;//可選
public Student(){
}
public int getStuId() {
return stuId;
}
public void setStuId(int stuId) {
this.stuId = stuId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
這種方法看上去可讀性和維護性比較好币厕,當我們使用這個類的時候只需要創(chuàng)建一個空的對象并且設(shè)置我們需要的屬性就可以了列另。比如這樣:
Student stu = new Student();
stu.setStuId(1);
stu.setName("小明");
stu.setAge(12);
這樣做有兩個問題,第一個問題是我們的stu對象沒有一個創(chuàng)建完畢的標識旦装,上面的stu對象我們設(shè)置了三個屬性页衙,但當別人看到這段代碼時,他不確定這個stu對象是只需要這三個屬性還是當時作者忘了寫完整,除非所有的屬性都給set上店乐,別人才能確保你這個對象創(chuàng)建完畢艰躺;另一個問題是任何人都可以在我們創(chuàng)建好的基礎(chǔ)上繼續(xù)改變它,也就是繼續(xù)給它set新的屬性或者刪除某個已經(jīng)set的屬性眨八,這就會使我們的stu對象具有可變性腺兴,這會引起潛在的風險。
好在我們還有第三種方法廉侧,那就是builder設(shè)計模式了页响。
public class Student {
private final int stuId;//必須
private final String name;//必須
private final int age;//可選
private final int gender;//可選
private final String address;//可選
private Student(StudentBuilder builder){
this.stuId = builder.stuId;
this.name = builder.name;
this.age = builder.age;
this.gender = builder.gender;
this.address = builder.address;
}
public int getStuId() {
return stuId;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int getGender() {
return gender;
}
public String getAddress() {
return address;
}
public static class StudentBuilder{
private final int stuId;
private final String name;
private int age;
private int gender;
private String address;
public StudentBuilder(int stuId,String name){
this.stuId = stuId;
this.name = name;
}
public StudentBuilder setAge(int age){
this.age = age;
return this;
}
public StudentBuilder setGender(int gender){
this.gender = gender;
return this;
}
public StudentBuilder setAddress(String address){
this.address = address;
return this;
}
public Student build(){
return new Student(this);
}
}
}
值得注意的幾點:
1.Student的構(gòu)造方法是私有的,也就是說我們不能直接new出Student對象
2.我們又將Student的屬性用final修飾了伏穆,并且我們在構(gòu)造方法中都為他們進行了初始化操作拘泞,我們只提供了getter方法
3.使用builder模式構(gòu)造出來的對象有更好的可讀性,等下我們會看到
4.StudentBuilder的屬性中只給我們必須的屬性添加的final修飾枕扫,所以我們必須在StudentBuilder的構(gòu)造方法中為他們初始化
使用builder設(shè)計模式完美的解決了方法一和方法二的不足陪腌,并且兼具他們的優(yōu)點:具有必填屬性和可選屬性的區(qū)分,更重要的是:可讀性很強烟瞧。唯一的不足是我們要在StudentBuilder中重復(fù)的寫一遍Student中的屬性诗鸭。
好,現(xiàn)在我們來創(chuàng)建一個Student對象吧
public Student getStudent(){
return new Student.StudentBuilder(1,"小明")//必填屬性在構(gòu)造方法中賦值
.setAge(1)//設(shè)置可選屬性 年齡
.setGender(1)//設(shè)置可選屬性 性別 默認1為男
.build();//對象構(gòu)建完畢的標識参滴,返回Student對象
}
非常優(yōu)雅有木有强岸?他是一個鏈式的調(diào)用,我們可以1行代碼就搞定砾赔,更重要的是蝌箍,他的可讀性非常強,而且通過build()我們可以很明確的告訴別人我們的Student已經(jīng)創(chuàng)建完畢暴心。
builder設(shè)計模式非常靈活妓盲,一個builder可以創(chuàng)建出各種各樣的對象,我們只需要在build()之前調(diào)用set方法來為我們的對象賦值专普。
builder模式另一個重要特性是:它可以對參數(shù)進行合法性驗證悯衬,如果我們傳入的參數(shù)無效,我們可以拋出一個IllegalStateException異常檀夹,但是我們在哪里進行參數(shù)合法性驗證也是有講究的:那就是在對象創(chuàng)建之后進行合法性驗證筋粗。我們修改StudentBuilder的build()方法
public Student build(){
Student student = new Student(this);
if (student.getAge()>120){
throw? new IllegalStateException("年齡超出限制");
}
return student;
}
為什么要先創(chuàng)建對象,再進行參數(shù)驗證炸渡?因為我們的StudentBuilder是線程不安全的娜亿,如果我們先進行參數(shù)驗證后創(chuàng)建對象,那么創(chuàng)建對象的時候?qū)ο蟮膶傩钥赡芤呀?jīng)被其他線程改變了蚌堵,例如下面的代碼就是錯誤的
/**
* 錯誤的
*/
public Student build(){
if (age>120){
throw? new IllegalStateException("年齡超出限制");
}
return new Student(this);
}
最后總結(jié)一下builder設(shè)計模式:當我們的類中有很多屬性的時候暇唾,更重要的是有很多可選屬性的時候,我們就可以使用builder設(shè)計模式,因為這樣不僅可以使我們的類使用起來很優(yōu)雅策州,而且還可以給我們的對象一個創(chuàng)建完成的標識,即build()方法宫仗。此種多是鏈式調(diào)用够挂,用起來方便,賞心悅目藕夫。
另外孽糖,可以看下AlertDialog的源碼,這里我就不貼出來了毅贮,太多办悟。
本文來源: http://blog.csdn.net/nugongahou110/article/details/50395698