實踐中的構建者模式

我將不深入討論該模式,因為已經有大量的帖子和書籍對此進行了詳細的解釋。 相反,我將告訴您為什么以及何時應該考慮使用它。 但是,值得一提的是,這種模式與《 四人幫》一書中介紹的模式有些不同。 雖然原始模式著重于抽象化構造步驟,以便通過更改所使用的構建器實現可以得到不同的結果,但本文中說明的模式用于消除不必要的復雜性,該復雜性是由多個構造函數,多個可選參數以及過度使用二傳手。

假設您有一個包含大量屬性的類,例如下面的User類。 假設您要使該類不可變(順便說一句,
除非有充分的理由不這樣做,否則您應該始終努力。 但是我們將在另一篇文章中討論)。

public class User {private final String firstName;    //requiredprivate final String lastName;    //requiredprivate final int age;    //optionalprivate final String phone;    //optionalprivate final String address;    //optional
...
}

現在,想象一下您的類中的某些屬性是必需的,而其他屬性是可選的。 您將如何構建此類的對象? 所有屬性都聲明為final,因此您必須在構造函數中全部設置它們,但您也想讓此類的客戶端有機會忽略可選屬性。

第一個有效的選擇是擁有一個僅將必需屬性作為參數的構造函數,一個將所有必需屬性加上第一個可選屬性作為參數的構造函數,而另一個將兩個可選屬性作為參數的構造函數,依此類推。 看起來像什么? 像這樣:

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;}

用這種方法構造類的對象的好處是它可以工作。 但是,這種方法的問題應該很明顯。 當您只有幾個屬性時,沒什么大不了的,但是隨著數量的增加,代碼變得更難以閱讀和維護。 更重要的是,對于客戶而言,代碼變得越來越難。 我應該作為客戶端調用哪個構造函數? 一個帶有2個參數? 一個3? 不傳遞顯式值的那些參數的默認值是多少? 如果我想為地址設置一個值而不是年齡和電話該怎么辦? 在那種情況下,我將不得不調用接受所有參數的構造函數,并為那些我不在乎的參數傳遞默認值。 此外,具有相同類型的幾個參數可能會造成混淆。 第一個String是電話號碼還是地址?

那么對于這些??情況我們還有什么選擇呢? 我們總是可以遵循JavaBeans約定,在該約定中,我們有一個默認的no-arg構造函數,并且每個屬性都有設置器和獲取器。 就像是:

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;}
}

這種方法似乎更易于閱讀和維護。 作為客戶端,我可以只創建一個空對象,然后僅設置我感興趣的屬性。那么這有什么問題呢? 該解決方案有兩個主要問題。 第一個問題與使此類的實例處于不一致狀態有關。 如果要創建一個具有其所有5個屬性值的User對象,則在調用所有setX方法之前,該對象將不具有完整狀態。 這意味著客戶端應用程序的某些部分可能會看到此對象,并假定該對象已經構造,而實際上并非如此。 這種方法的第二個缺點是現在User類是可變的。 您失去了不可變對象的所有好處。

幸運的是,對于這些情況,還有第三種選擇,即構建器模式。 解決方案將如下所示。

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);}}
}

需要注意的幾個要點:

  • User構造函數是私有的,這意味著不能從客戶端代碼直接實例化此類。
  • 該類再次是不可變的。 所有屬性都是最終屬性,它們是在構造函數上設置的。 此外,我們僅為他們提供吸氣劑。
  • 構建器使用Fluent Interface慣用法使客戶機代碼更具可讀性(我們稍后將看到一個示例)。
  • 構建器構造函數僅接收必需的屬性,并且此屬性是在構建器上唯一定義為“最終”的屬性,以確保在構造函數上設置其值。

生成器模式的使用具有我一開始提到的前兩種方法的所有優點,并且沒有任何缺點。 客戶端代碼更易于編寫,更重要的是易于閱讀。 我聽到的關于該模式的唯一批評是,您必須在構建器上復制類的屬性。 但是,考慮到構建器類通常是它構建的類的靜態成員類,因此它們可以很容易地一起進化。

現在,嘗試創建新的User對象的客戶端代碼如何? 讓我們來看看:

public User getUser() {return newUser.UserBuilder('Jhon', 'Doe').age(30).phone('1234567').address('Fake address 1234').build();}

很整潔,不是嗎? 您可以用1行代碼構建一個User對象,最重要的是,它很容易閱讀。 而且,您要確保每當獲得此類的對象時,都不會處于不完整狀態。

這種模式非常靈活。 通過在對“ build”方法的調用之間更改構建器屬性,可以使用單個構建器來創建多個對象。 構建器甚至可以自動完成每次調用之間生成的某些字段,例如ID或序列號。

重要的一點是,就像構造器一樣,構造器可以對其參數施加不變性。 構建方法可以檢查這些不變量,如果它們無效則拋出IllegalStateException
將參數從構建器復制到對象之后,必須檢查它們,并在對象字段而不是構建器字段上檢查它們,這一點至關重要。 這樣做的原因是,由于生成器不是線程安全的,因此,如果我們在實際創建對象之前檢查參數,則可以在檢查參數與復制參數之間的另一個線程更改它們的值。 該時間段稱為“漏洞窗口”。 在我們的用戶示例中,這可能類似于以下內容:

public User build() {User user = new user(this);if (user.getAge() 120) {throw new IllegalStateException(“Age out of range”); // thread-safe}return user;
}

以前的版本是線程安全的,因為我們首先創建用戶,然后檢查不可變對象上的不變量。 以下代碼在功能上看起來相同,但是它不是線程安全的,因此應避免執行以下操作:

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);
}

這種模式的最后一個優點是可以將構建器傳遞給某個方法,以使該方法能夠為客戶端創建一個或多個對象,而該方法無需了解有關如何創建對象的任何種類的細節。 為此,通常需要一個簡單的界面,例如:

public interface Builder {T build();
}

在前面的User示例中, UserBuilder類可以實現Builder <User> 。 然后,我們可能會有類似的內容:

UserCollection buildUserCollection(Builder<? extends User> userBuilder){...}

好吧,那是一篇相當長的第一篇文章。 綜上所述,對于具有多個參數的類(不是一門精確的科學,但我通常將4個屬性用作使用該模式的一個很好的指標),Builder模式是一個絕佳的選擇,尤其是當這些參數中的大多數是可選的。 您將獲得易于閱讀,編寫和維護的客戶端代碼。 此外,您的類可以保持不變,這使您的代碼更安全。

更新 :如果將Eclipse用作IDE,事實證明您有很多插件可以避免該模式隨附的大多數樣板代碼。 我見過的三個是:

  • 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/

我沒有親自嘗試過其中任何一個,因此我無法真正做出明智的決定。 我認為其他IDE應該也存在類似的插件。

參考:開發 人員實踐中來自JCG合作伙伴 Jose Luis 的構建者模式, 它應該是博客。

翻譯自: https://www.javacodegeeks.com/2013/01/the-builder-pattern-in-practice.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/370100.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/370100.shtml
英文地址,請注明出處:http://en.pswp.cn/news/370100.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

python計算汽車的平均油耗_用python對汽車油耗進行數據分析

原標題&#xff1a;用python對汽車油耗進行數據分析- 從http://fueleconomy.gov/geg/epadata/vehicles.csv.zip下載汽車油耗數據集并解壓- 進入jupyter notebook(ipython notebook)并新建一個New Notebook- 輸入命令[python]view plaincopyimportpandas as pdimportnumpy as np…

git常用命令2

##一、git常用命令 ###1、 push文件 * 打開cmd窗口 * 輸入f:&#xff0c;進入f:&#xff08;自己隨便在自己的電腦上找個位置就行了&#xff0c;這里的f:&#xff0c;表示的是f盤&#xff09; * 然后輸入mkdir workSpace&#xff0c;會自動在f盤下生成一個workSpace文件夾 * 然…

android移動應用基礎教程源代碼,Android移動應用基礎教程 【程序活動單元Activity】...

本章目錄一、Activity的生命周期1、生命周期狀態2 、生命周期方法3、橫豎屏切換時的生命周期二、Activity的創建配置和關閉1、Activity的創建2、配置Activity3、開啟和關閉Activity三、Intent與IntentFilter1、Intent介紹1.1 意圖的概念1.2 顯式意圖1.3 隱式意圖2、IntentFilte…

elasticsearch中cluster和transport知識

elasticsearch cluster 概述 elasticsearch節點間通信的基礎transport轉載于:https://www.cnblogs.com/wzj4858/p/8126033.html

Python中使用subplot在一張畫布上顯示多張圖

subplot(arg1, arg2, arg3) arg1: 在垂直方向同時畫幾張圖arg2: 在水平方向同時畫幾張圖arg3: 當前命令修改的是第幾張圖 t np.arange(0,5,0.1) y1 np.sin(2*np.pi*t) y2 np.sin(2*np.pi*t) plt.subplot(211) plt.plot(t,y1,b-.) plt.subplot(212) plt.plot(t,y2,r--) plt.s…

Java 8:從PermGen到元空間

您可能已經知道&#xff0c;現在可以下載JDK 8 Early Access 。 這使Java開發人員可以嘗試Java 8的一些新語言和運行時功能。這些功能之一是完全刪除自Oracle自JDK 7發行以來就宣布的Permanent Generation&#xff08;PermGen&#xff09;空間。例如&#xff0c;自JDK 7起&…

oracle symonym_ORACLE SYNONYM詳解

以下內容整理自Oracle 官方文檔一 概念A synonym is an alias for any table, view,materialized view, sequence, procedure, function, package, type, Java classschema object, user-defined object type, or another synonym. Because a synonymis simply an alias, it re…

瀏覽器緩存問題原理以及解決方案

瀏覽器緩存問題&#xff1a; 簡單來說&#xff0c;瀏覽器緩存就是把一個已經請求過的Web資源&#xff08;如html頁面&#xff0c;圖片&#xff0c;js&#xff0c;數據等&#xff09;拷貝一份副本儲存在瀏覽器中。緩存會根據進來的請求保存輸出內容的副本。當下一個請求來到的時…

Scikit-Learn機器學習入門

現在最常用的數據分析的編程語言為R和Python。每種語言都有自己的特點&#xff0c;Python因為Scikit-Learn庫贏得了優勢。Scikit-Learn有完整的文檔&#xff0c;并實現很多機器學習算法&#xff0c;而每種算法使用的接口幾乎相同&#xff0c;可以非常快的測試其它學習算法。 Pa…

hdu1542 Atlantis(掃描線+線段樹+離散)矩形相交面積

題目鏈接&#xff1a;點擊打開鏈接 題目描寫敘述&#xff1a;給定一些矩形&#xff0c;求這些矩形的總面積。假設有重疊。僅僅算一次 解題思路&#xff1a;掃描線線段樹離散&#xff08;代碼從上往下掃描&#xff09; 代碼&#xff1a; #include<cstdio> #include <al…

瀏覽器滾動條 --- 自定義“衣裳”

由于種種原因&#xff0c;瀏覽器的默認滾動條“衣裳”實在是 (ˉ▽&#xffe3;&#xff5e;)~~&#xff0c;為了“美”&#xff0c;本人結合萬維網各大神給的經驗和自己的實踐&#xff0c;做了此篇總結。若有錯誤&#xff0c;請在評論里給出&#xff0c;我會及時更改。 我在電…

電腦調分辨率黑屏了怎么辦_調顯示器分辨率黑屏怎么辦

調顯示器分辨率黑屏怎么辦調顯示器分辨率黑屏解決方法&#xff1a;1&#xff0c;開機&#xff0c;當快要進入系統選項時&#xff0c;立即按f8鍵進入“高級模式”&#xff0c;因為系統選項界面顯示的時間非常短&#xff0c;可以提早按f8鍵&#xff0c;否則錯過時機就得重來。2&a…

什么是JNDI,SPI,CCI,LDAP和JCA?

JNDI代表Java命名和目錄接口 。 它是用于提供對目錄服務&#xff08;即帶有對象的服務映射名稱&#xff08;字符串&#xff09;&#xff0c;對遠程對象或簡單數據的引用&#xff09;的訪問的API。 這就是所謂的 約束力 。 綁定集稱為上下文 。 應用程序使用JNDI接口訪問資源。…

android studio gradle 學習,學習Android Studio里的Gradle

一直聽說Gradle很強大&#xff0c;只是偶爾用Android Studio創建Demo的時候看到他一次&#xff0c;今天抽個時間完整記錄一下。1.gradle位置Android Studio項目創建好之后&#xff0c;默認有3個gradle文件&#xff0c;分別位于&#xff1a;/settings.gradle/build.gradle/app/b…

接口耗時打印并統計

1.可以利用Tomcat的access-log日志&#xff0c;讓其打印出http請求的每次耗時。可以在 config/server.xml里Host標簽下配置tomcat訪問日志格式 <Valve className"org.apache.catalina.valves.AccessLogValve" directory"logs" prefix&quo…

js內存

js在定義變量時完成了內存的分配 js具有自動垃圾回收機制&#xff0c;垃圾回收器會每隔固定的一段時間就執行一次釋放操作&#xff0c;即找出那些不再繼續使用的值&#xff0c;釋放其占用的內存 js中最常用的是通過標記清除的算法來找到哪些對象是不再繼續使用的&#xff0c;因…

halcon 圖像差分_Halcon編程-基于紋理的mara檢測

表面瑕疵檢測是機器視覺領域非常重要的一個應用。機器視覺是集光學、機電和計算機三個領域的一門不算新的技術。但目前表面瑕疵檢測在學界主要是計算機專業或者控制專業瞄準圖像處理方向在做&#xff0c;而視覺光學系統這一塊主要是光學工程專業在做。很少有研究者把這三塊都結…

Apache Camel入門

在先前的博文中&#xff0c;我們了解了企業集成模式&#xff08;EIP&#xff09;。 現在&#xff0c;在這篇文章中&#xff0c;我們將研究實現這些模式的Apache Camel框架。 關于駱駝&#xff1a; Apache Camel是一個開放源代碼項目&#xff0c;已有將近5年的歷史&#xff0c;…

css 寫打印樣式問題

&#xff08;1&#xff09;背景顏色打印不出來問題解決方法 background樣式要加上 !important&#xff1b;color樣式要加上 !important&#xff1b;-webkit-print-color-adjust: exact;然后記得瀏覽器打印設置里面要在“打印背景圖形”前面打勾。 -webkit-print-color-adjust:…

android studio smssdk,SMSSDK for Android 配置

1.集成之前先要申請Mob的appkey與appsecret2.在Mob官網下載最新SDK&#xff0c;解壓后會看到以下目錄結構&#xff1a;SMSSDK下存放的是短信SDK的全部內容。3.在android studio中加入SMS的第三方庫AS版本的SMSSDK目錄下包含以下內容&#xff1a;MobCommons.jar&#xff1a;Mob …