Java設(shè)計(jì)模式筆記之Builder模式
當(dāng)我第一次使用Picasso的時(shí)候,看見(jiàn)下面的官網(wǎng)示例時(shí),我和我的小伙伴都驚呆了!
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
如此簡(jiǎn)潔明了的使用方式,如此靈活多變的鏈?zhǔn)秸{(diào)用,讓我深深地迷住了,然后我一直苦苦追求它,奈何天資愚笨,不知如何掀起它的神秘面紗...
不好意思,中二病又犯了,重來(lái)一遍。經(jīng)過(guò)不懈的努力,終于發(fā)現(xiàn)它就是傳說(shuō)中的Builder(建造者)模式,并學(xué)會(huì)了如何與它親密相處。
Builder模式是怎么來(lái)的
不知道是哪位賢人曾經(jīng)說(shuō)過(guò),存在即為合理。Builder模式在眾多的框架以及android原生代碼中存在(比如AlertDialog),就一定有其價(jià)值,用來(lái)解決某些需求。
考慮這樣一個(gè)場(chǎng)景,假如有一個(gè)類(lèi)(****User****),里面有很多屬性,并且你希望這些類(lèi)的屬性都是不可變的(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ù) }
在這個(gè)類(lèi)中,有些參數(shù)是必要的,而有些參數(shù)是非必要的。就好比在注冊(cè)用戶(hù)時(shí),用戶(hù)的姓和名是必填的,而年齡、手機(jī)號(hào)和家庭地址等是非必需的。那么問(wèn)題就來(lái)了,如何創(chuàng)建這個(gè)類(lèi)的對(duì)象呢?
一種可行的方案就是實(shí)用構(gòu)造方法。第一個(gè)構(gòu)造方法只包含兩個(gè)必需的參數(shù),第二個(gè)構(gòu)造方法中,增加一個(gè)可選參數(shù),第三個(gè)構(gòu)造方法中再增加一個(gè)可選參數(shù),依次類(lèi)推,直到構(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; }
這樣做的好處只有一個(gè):可以成功運(yùn)行。但是弊端很明顯:
- 參數(shù)較少的時(shí)候問(wèn)題還不大,一旦參數(shù)多了,代碼可讀性就很差,并且難以維護(hù)。
- 對(duì)調(diào)用者來(lái)說(shuō)也很麻煩。如果我只想多傳一個(gè)address參數(shù),還必需給age、phone設(shè)置默認(rèn)值。而且調(diào)用者還會(huì)有這樣的困惑:我怎么知道第四個(gè)String類(lèi)型的參數(shù)該傳address還是phone?
第二種解決辦法就出現(xiàn)了,我們同樣可以根據(jù)JavaBean的習(xí)慣,設(shè)置一個(gè)空參數(shù)的構(gòu)造方法,然后為每一個(gè)屬性設(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; } }
這種方法看起來(lái)可讀性不錯(cuò),而且易于維護(hù)。作為調(diào)用者,創(chuàng)建一個(gè)空的對(duì)象,然后只需傳入我感興趣的參數(shù)。那么缺點(diǎn)呢?也有兩點(diǎn):
- 對(duì)象會(huì)產(chǎn)生不一致的狀態(tài)。當(dāng)你想要傳入5個(gè)參數(shù)的時(shí)候,你必需將所有的setXX方法調(diào)用完成之后才行。然而一部分的調(diào)用者看到了這個(gè)對(duì)象后,以為這個(gè)對(duì)象已經(jīng)創(chuàng)建完畢,就直接食用了,其實(shí)User對(duì)象并沒(méi)有創(chuàng)建完成。
- ****User****類(lèi)是可變的了,不可變類(lèi)所有好處都不復(fù)存在。
終于輪到主角上場(chǎng)的時(shí)候了,利用Builder模式,我們可以解決上面的問(wèn)題,代碼如下:
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); } } }
有幾個(gè)重要的地方需要強(qiáng)調(diào)一下:
- ****User****類(lèi)的構(gòu)造方法是私有的。也就是說(shuō)調(diào)用者不能直接創(chuàng)建User對(duì)象。
- ****User****類(lèi)的屬性都是不可變的。所有的屬性都添加了final修飾符,并且在構(gòu)造方法中設(shè)置了值。并且,對(duì)外只提供getters方法。
- Builder模式使用了鏈?zhǔn)秸{(diào)用??勺x性更佳。
- Builder的內(nèi)部類(lèi)構(gòu)造方法中只接收必傳的參數(shù),并且該必傳的參數(shù)適用了final修飾符。
相比于前面兩種方法,Builder模式擁有其所有的優(yōu)點(diǎn),而沒(méi)有上述方法中的缺點(diǎn)。客戶(hù)端的代碼更容易寫(xiě),并且更重要的是,可讀性非常好。唯一可能存在的問(wèn)題就是會(huì)產(chǎn)生多余的Builder對(duì)象,消耗內(nèi)存。然而大多數(shù)情況下我們的Builder內(nèi)部類(lèi)使用的是靜態(tài)修飾的(static),所以這個(gè)問(wèn)題也沒(méi)多大關(guān)系。
現(xiàn)在,讓我們看看如何創(chuàng)建一個(gè)User對(duì)象呢?
new User.UserBuilder("王", "小二") .age(20) .phone("123456789") .address("亞特蘭蒂斯大陸") .build();
相當(dāng)整潔,不是嗎?你甚至可以用一行代碼完成對(duì)象的創(chuàng)建。
關(guān)于Builder的一點(diǎn)說(shuō)明
線(xiàn)程安全問(wèn)題
由于Builder是非線(xiàn)程安全的,所以如果要在Builder內(nèi)部類(lèi)中檢查一個(gè)參數(shù)的合法性,必需要在對(duì)象創(chuàng)建完成之后再檢查。
public User build() { User user = new user(this); if (user.getAge() > 120) { throw new IllegalStateException(“Age out of range”); // 線(xiàn)程安全 } return user; }
上面的寫(xiě)法是正確的,而下面的代碼是非線(xiàn)程安全的:
public User build() { if (age > 120) { throw new IllegalStateException(“Age out of range”); // 非線(xiàn)程安全 } return new User(this); }
經(jīng)典的Builder模式
上面介紹的Builder模式當(dāng)然不是“原生態(tài)”的啦,經(jīng)典的Builder模式的類(lèi)圖如下:
builder
其中:
- Product 產(chǎn)品抽象類(lèi)。
- Builder 抽象的Builder類(lèi)。
- ConcretBuilder 具體的Builder類(lèi)。
- Director 同一組裝過(guò)程。
當(dāng)然,之前實(shí)例中的Builder模式,是省略掉了Director的,這樣結(jié)構(gòu)更加簡(jiǎn)單。所以在很多框架源碼中,涉及到Builder模式時(shí),大多都不是經(jīng)典GOF的Builder模式,而是省略后的。
總結(jié)
到此這篇關(guān)于Java設(shè)計(jì)模式筆記之Builder模式的文章就介紹到這了,更多相關(guān)Java設(shè)計(jì)模式Builder模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring注解驅(qū)動(dòng)之ApplicationListener用法解讀
這篇文章主要介紹了Spring注解驅(qū)動(dòng)之ApplicationListener用法解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09java網(wǎng)絡(luò)之基于UDP的聊天程序示例解析
這篇文章主要介紹了java網(wǎng)絡(luò)之基于UDP的聊天程序示例解析,文中通過(guò)步驟及示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-085分鐘快速創(chuàng)建spring boot項(xiàng)目的完整步驟
這篇文章主要給大家介紹了關(guān)于通過(guò)5分鐘快速創(chuàng)建spring boot項(xiàng)目的完整步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06Java多線(xiàn)程及分布式爬蟲(chóng)架構(gòu)原理解析
這篇文章主要介紹了Java多線(xiàn)程及分布式爬蟲(chóng)架構(gòu)原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10Spring AOP 自定義注解的實(shí)現(xiàn)代碼
本篇文章主要介紹了Spring AOP 自定義注解的實(shí)現(xiàn)代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04SpringBoot使用JTA實(shí)現(xiàn)對(duì)多數(shù)據(jù)源的事務(wù)管理
了解事務(wù)的都知道,在我們?nèi)粘i_(kāi)發(fā)中單單靠事務(wù)管理就可以解決絕大多數(shù)問(wèn)題了,但是為啥還要提出JTA這個(gè)玩意呢,到底JTA是什么呢?他又是具體來(lái)解決啥問(wèn)題的呢?本文小編就給大家介紹一下如何在Spring Boot中使用JTA實(shí)現(xiàn)對(duì)多數(shù)據(jù)源的事務(wù)管理2023-11-11