工厂模式 构建者模式_实践中的构建者模式
工廠模式 構(gòu)建者模式
我將不深入討論該模式,因?yàn)橐呀?jīng)有大量的文章和書籍對此進(jìn)行了詳細(xì)的解釋。 相反,我將告訴您為什么以及何時應(yīng)該考慮使用它。 但是,值得一提的是,這種模式與《 四人幫》一書中介紹的模式有些不同。 雖然原始模式著重于抽象化構(gòu)造步驟,以便通過更改所使用的構(gòu)建器實(shí)現(xiàn)可以得到不同的結(jié)果,但本文中說明的模式用于消除不必要的復(fù)雜性,該復(fù)雜性是由多個構(gòu)造函數(shù),多個可選參數(shù)以及過度使用二傳手。
假設(shè)您有一個包含大量屬性的類,例如下面的User類。 假設(shè)您要使該類不可變(順便說一句,
除非有充分的理由不這樣做,否則您應(yīng)該始終努力。 但是我們將在另一篇文章中討論)。
現(xiàn)在,想象一下類中的某些屬性是必需的,而其他屬性是可選的。 您將如何構(gòu)建此類的對象? 所有屬性都聲明為final,因此您必須在構(gòu)造函數(shù)中全部設(shè)置它們,但您還想讓此類的客戶端有機(jī)會忽略可選屬性。
第一個有效的選擇是擁有一個僅將必需屬性作為參數(shù)的構(gòu)造函數(shù),一個將所有必需屬性加上第一個可選屬性作為參數(shù)的構(gòu)造函數(shù),而另一個則將兩個可選屬性作為參數(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òu)造類的對象的好處是它可以工作。 但是,這種方法的問題應(yīng)該很明顯。 當(dāng)您只有幾個屬性時,沒什么大不了的,但是隨著數(shù)量的增加,代碼變得更難以閱讀和維護(hù)。 更重要的是,對于客戶而言,代碼變得越來越難。 我應(yīng)該作為客戶端調(diào)用哪個構(gòu)造函數(shù)? 一個帶有2個參數(shù)? 一個3? 不傳遞顯式值的那些參數(shù)的默認(rèn)值是多少? 如果我想為地址設(shè)置一個值而不是年齡和電話該怎么辦? 在那種情況下,我將不得不調(diào)用接受所有參數(shù)的構(gòu)造函數(shù),并為那些我不在乎的參數(shù)傳遞默認(rèn)值。 此外,具有相同類型的幾個參數(shù)可能會造成混淆。 第一個String是電話號碼還是地址?
那么對于這些??情況我們還有什么選擇呢? 我們總是可以遵循JavaBeans約定,在該約定中,我們有一個默認(rèn)的no-arg構(gòu)造函數(shù),并且每個屬性都有設(shè)置器和獲取器。 就像是:
public class User {private String firstName; // requiredprivate String lastName; // requiredprivate int age; // optionalprivate String phone; // optionalprivate String address; //optionalpublic String getFirstName() {return firstName;}public void setFirstName(String firstName) {this.firstName = firstName;}public String getLastName() {return lastName;}public void setLastName(String lastName) {this.lastName = lastName;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;} }這種方法似乎更易于閱讀和維護(hù)。 作為客戶端,我可以只創(chuàng)建一個空對象,然后僅設(shè)置我感興趣的屬性。那么這有什么問題呢? 該解決方案有兩個主要問題。 第一個問題與使此類的實(shí)例處于不一致狀態(tài)有關(guān)。 如果要創(chuàng)建一個具有其所有5個屬性值的User對象,則在調(diào)用所有setX方法之前,該對象將不具有完整狀態(tài)。 這意味著客戶端應(yīng)用程序的某些部分可能會看到此對象,并假定該對象已被構(gòu)造,而實(shí)際上并非如此。 這種方法的第二個缺點(diǎn)是現(xiàn)在User類是可變的。 您失去了不可變對象的所有好處。
幸運(yùn)的是,對于這些情況,還有第三種選擇,即構(gòu)建器模式。 解決方案將如下所示。
public class User {private final String firstName; // requiredprivate final String lastName; // requiredprivate final int age; // optionalprivate final String phone; // optionalprivate final String address; // optionalprivate 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);}} }值得注意的幾個要點(diǎn):
- User構(gòu)造函數(shù)是私有的,這意味著不能從客戶端代碼直接實(shí)例化此類。
- 該類再次是不可變的。 所有屬性都是最終屬性,它們是在構(gòu)造函數(shù)上設(shè)置的。 此外,我們僅為他們提供吸氣劑。
- 構(gòu)建器使用Fluent Interface慣用法使客戶機(jī)代碼更具可讀性(我們稍后將看到一個示例)。
- 構(gòu)建器構(gòu)造函數(shù)僅接收必需的屬性,并且此屬性是在構(gòu)建器上唯一定義為“最終”的屬性,以確保在構(gòu)造函數(shù)上設(shè)置其值。
使用構(gòu)建器模式具有我一開始提到的前兩種方法的所有優(yōu)點(diǎn),但沒有任何缺點(diǎn)。 客戶端代碼更易于編寫,更重要的是易于閱讀。 我聽到的關(guān)于該模式的唯一批評是,您必須在構(gòu)建器上復(fù)制類的屬性。 但是,考慮到構(gòu)建器類通常是它構(gòu)建的類的靜態(tài)成員類,因此它們可以很容易地一起進(jìn)化。
現(xiàn)在,嘗試創(chuàng)建新的User對象的客戶端代碼如何? 讓我們來看看:
public User getUser() {return newUser.UserBuilder('Jhon', 'Doe').age(30).phone('1234567').address('Fake address 1234').build();}很整潔,不是嗎? 您可以用1行代碼構(gòu)建一個User對象,最重要的是,它很容易閱讀。 此外,您要確保每當(dāng)獲得此類的對象時,都不會處于不完整狀態(tài)。
這種模式非常靈活。 通過在對“ build”方法的調(diào)用之間更改構(gòu)建器屬性,可以使用單個構(gòu)建器來創(chuàng)建多個對象。 構(gòu)建器甚至可以自動完成每次調(diào)用之間生成的某些字段,例如ID或序列號。
重要的一點(diǎn)是,就像構(gòu)造器一樣,構(gòu)造器可以對其參數(shù)施加不變性。 生成方法可以檢查這些不變量,如果它們無效則拋出IllegalStateException 。
將參數(shù)從構(gòu)建器復(fù)制到對象之后,必須檢查它們,并在對象字段而不是構(gòu)建器字段上檢查它們,這一點(diǎn)至關(guān)重要。 這樣做的原因是,由于構(gòu)建器不是線程安全的,因此,如果我們在實(shí)際創(chuàng)建對象之前檢查參數(shù),則可以在檢查參數(shù)和復(fù)制參數(shù)之間由另一個線程更改其值。 這一段時間稱為“漏洞窗口”。 在我們的用戶示例中,這可能類似于以下內(nèi)容:
以前的版本是線程安全的,因?yàn)槲覀兪紫葎?chuàng)建用戶,然后檢查不可變對象上的不變量。 以下代碼在功能上看起來相同,但不是線程安全的,因此應(yīng)避免執(zhí)行以下操作:
public User build() {if (age 120) {throw new IllegalStateException(“Age out of range”); // bad, not thread-safe}// This is the window of opportunity for a second thread to modify the value of agereturn new User(this); }這種模式的最后一個優(yōu)點(diǎn)是可以將構(gòu)建器傳遞給某個方法,以使該方法能夠?yàn)榭蛻舳藙?chuàng)建一個或多個對象,而該方法無需知道有關(guān)如何創(chuàng)建對象的任何種類的細(xì)節(jié)。 為此,通常需要一個簡單的界面,例如:
public interface Builder {T build(); }在前面的User示例中, UserBuilder類可以實(shí)現(xiàn)Builder <User> 。 然后,我們可能會有類似的內(nèi)容:
UserCollection buildUserCollection(Builder<? extends User> userBuilder){...}好吧,那是一篇相當(dāng)長的第一篇文章。 綜上所述,對于具有多個參數(shù)的類(不是一門確切的科學(xué)方法,但我通常將4個屬性用作使用該模式的一個很好的指標(biāo)),Builder模式是一個很好的選擇,尤其是當(dāng)這些參數(shù)中的大多數(shù)是可選的。 您將獲得易于閱讀,編寫和維護(hù)的客戶端代碼。 此外,您的類可以保持不變,這使您的代碼更安全。
更新 :如果將Eclipse用作IDE,事實(shí)證明您有很多插件可以避免該模式隨附的大多數(shù)樣板代碼。 我見過的三個是:
- http://code.google.com/p/bpep/
- http://code.google.com/a/eclipselabs.org/p/bob-the-builder/
- http://code.google.com/p/fluent-builders-generator-eclipse-plugin/
我沒有親自嘗試過其中任何一個,因此我無法真正做出明智的決定。 我認(rèn)為其他IDE應(yīng)該也存在類似的插件。
參考:開發(fā) 人員實(shí)踐中來自JCG合作伙伴 Jose Luis 的構(gòu)建者模式, 它應(yīng)該是博客。
翻譯自: https://www.javacodegeeks.com/2013/01/the-builder-pattern-in-practice.html
工廠模式 構(gòu)建者模式
總結(jié)
以上是生活随笔為你收集整理的工厂模式 构建者模式_实践中的构建者模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 苹果7功能有哪些
- 下一篇: neo4j cypher_优化Neo4j