當(dāng)我第一次使用Picasso的時候烹骨,看見下面的官網(wǎng)示例時吨岭,我和我的小伙伴都驚呆了!
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
如此簡潔明了的使用方式急灭,如此靈活多變的鏈?zhǔn)秸{(diào)用,讓我深深地迷住了畴嘶,然后我一直苦苦追求它,奈何天資愚笨蒋院,不知如何掀起它的神秘面紗铸屉,直到我在網(wǎng)上找到了這篇教程...
不好意思顷啼,中二病又犯了茵瀑,重來一遍。經(jīng)過不懈的努力,終于發(fā)現(xiàn)它就是傳說中的Builder(建造者)模式匙奴,并學(xué)會了如何與它親密相處。
Builder模式是怎么來的
不知道是哪位賢人曾經(jīng)說過,存在即為合理焊刹。Builder模式在眾多的框架以及android原生代碼中存在(比如AlertDialog),就一定有其價值非凌,用來解決某些需求颁糟。
考慮這樣一個場景棱貌,假如有一個類(****User****)勺像,里面有很多屬性吟宦,并且你希望這些類的屬性都是不可變的(final)袁波,就像下面的代碼:
public class User {
private final String firstName; // 必傳參數(shù)
private final String lastName; // 必傳參數(shù)
private final int age; // 可選參數(shù)
private final String phone; // 可選參數(shù)
private final String address; // 可選參數(shù)
}
在這個類中娃磺,有些參數(shù)是必要的偷卧,而有些參數(shù)是非必要的蚕泽。就好比在注冊用戶時仔蝌,用戶的姓和名是必填的绰更,而年齡执俩、手機(jī)號和家庭地址等是非必需的宋税。那么問題就來了矮台,如何創(chuàng)建這個類的對象呢蛤迎?
一種可行的方案就是實用構(gòu)造方法辆童。第一個構(gòu)造方法只包含兩個必需的參數(shù),第二個構(gòu)造方法中,增加一個可選參數(shù),第三個構(gòu)造方法中再增加一個可選參數(shù)使套,依次類推奉呛,直到構(gòu)造方法中包含了所有的參數(shù)咆槽。
public User(String firstName, String lastName) {
this(firstName, lastName, 0);
}
public User(String firstName, String lastName, int age) {
this(firstName, lastName, age, "");
}
public User(String firstName, String lastName, int age, String phone) {
this(firstName, lastName, age, phone, "");
}
public User(String firstName, String lastName, int age, String phone, String address) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.phone = phone;
this.address = address;
}
這樣做的好處只有一個:可以成功運(yùn)行。但是弊端很明顯:
- 參數(shù)較少的時候問題還不大,一旦參數(shù)多了,代碼可讀性就很差谬擦,并且難以維護(hù)惨远。
- 對調(diào)用者來說也很麻煩。如果我只想多傳一個
address
參數(shù)蔑水,還必需給age
歇父、phone
設(shè)置默認(rèn)值垂睬。而且調(diào)用者還會有這樣的困惑:我怎么知道第四個String
類型的參數(shù)該傳address
還是phone
檐春?
第二種解決辦法就出現(xiàn)了俐巴,我們同樣可以根據(jù)JavaBean的習(xí)慣缘圈,設(shè)置一個空參數(shù)的構(gòu)造方法遣疯,然后為每一個屬性設(shè)置setters
和getters
方法文判。就像下面一樣:
public class User {
private String firstName; // 必傳參數(shù)
private String lastName; // 必傳參數(shù)
private int age; // 可選參數(shù)
private String phone; // 可選參數(shù)
private String address; // 可選參數(shù)
public User() {
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
public String getPhone() {
return phone;
}
public String getAddress() {
return address;
}
}
這種方法看起來可讀性不錯赏殃,而且易于維護(hù)抗蠢。作為調(diào)用者,創(chuàng)建一個空的對象,然后只需傳入我感興趣的參數(shù)笔宿。那么缺點(diǎn)呢捧存?也有兩點(diǎn):
- 對象會產(chǎn)生不一致的狀態(tài)。當(dāng)你想要傳入5個參數(shù)的時候,你必需將所有的
setXX
方法調(diào)用完成之后才行闲孤。然而一部分的調(diào)用者看到了這個對象后舆绎,以為這個對象已經(jīng)創(chuàng)建完畢波岛,就直接食用了,其實User對象并沒有創(chuàng)建完成曹鸠。 - ****User****類是可變的了眠屎,不可變類所有好處都不復(fù)存在橄镜。
終于輪到主角上場的時候了,利用Builder模式舔亭,我們可以解決上面的問題肢预,代碼如下:
public class User {
private final String firstName; // 必傳參數(shù)
private final String lastName; // 必傳參數(shù)
private final int age; // 可選參數(shù)
private final String phone; // 可選參數(shù)
private final String address; // 可選參數(shù)
private User(UserBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
public String getPhone() {
return phone;
}
public String getAddress() {
return address;
}
public static class UserBuilder {
private final String firstName;
private final String lastName;
private int age;
private String phone;
private String address;
public UserBuilder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public UserBuilder phone(String phone) {
this.phone = phone;
return this;
}
public UserBuilder address(String address) {
this.address = address;
return this;
}
public User build() {
return new User(this);
}
}
}
有幾個重要的地方需要強(qiáng)調(diào)一下:
- ****User****類的構(gòu)造方法是私有的辫红。也就是說調(diào)用者不能直接創(chuàng)建User對象厉熟。
- ****User****類的屬性都是不可變的滤馍。所有的屬性都添加了final修飾符阁苞,并且在構(gòu)造方法中設(shè)置了值。并且蜻展,對外只提供
getters
方法纵顾。 - Builder模式使用了鏈?zhǔn)秸{(diào)用《绊铮可讀性更佳施逾。
- Builder的內(nèi)部類構(gòu)造方法中只接收必傳的參數(shù),并且該必傳的參數(shù)適用了final修飾符。
相比于前面兩種方法汉额,Builder模式擁有其所有的優(yōu)點(diǎn)曹仗,而沒有上述方法中的缺點(diǎn)∪渌眩客戶端的代碼更容易寫怎茫,并且更重要的是,可讀性非常好妓灌。唯一可能存在的問題就是會產(chǎn)生多余的Builder對象轨蛤,消耗內(nèi)存。然而大多數(shù)情況下我們的Builder內(nèi)部類使用的是靜態(tài)修飾的(static)虫埂,所以這個問題也沒多大關(guān)系祥山。
現(xiàn)在,讓我們看看如何創(chuàng)建一個User
對象呢掉伏?
new User.UserBuilder("王", "小二")
.age(20)
.phone("123456789")
.address("亞特蘭蒂斯大陸")
.build();
相當(dāng)整潔缝呕,不是嗎?你甚至可以用一行代碼完成對象的創(chuàng)建斧散。
關(guān)于Builder的一點(diǎn)說明
線程安全問題
由于Builder是非線程安全的供常,所以如果要在Builder內(nèi)部類中檢查一個參數(shù)的合法性,必需要在對象創(chuàng)建完成之后再檢查颅湘。
public User build() {
User user = new user(this);
if (user.getAge() > 120) {
throw new IllegalStateException(“Age out of range”); // 線程安全
}
return user;
}
上面的寫法是正確的话侧,而下面的代碼是非線程安全的:
public User build() {
if (age > 120) {
throw new IllegalStateException(“Age out of range”); // 非線程安全
}
return new User(this);
}
經(jīng)典的Builder模式
上面介紹的Builder模式當(dāng)然不是“原生態(tài)”的啦,經(jīng)典的Builder模式的類圖如下:
其中:
- Product 產(chǎn)品抽象類闯参。
- Builder 抽象的Builder類瞻鹏。
- ConcretBuilder 具體的Builder類。
- Director 同一組裝過程鹿寨。
當(dāng)然新博,之前實例中的Builder模式,是省略掉了Director的脚草,這樣結(jié)構(gòu)更加簡單赫悄。所以在很多框架源碼中,涉及到Builder模式時馏慨,大多都不是經(jīng)典GOF的Builder模式埂淮,而是省略后的。