###### tags: `Java` `Design Pattern` `Tutorials` # 建立者模式(Builder Pattern) ## 一、前言 在物件導向程式設計中,我們很常遇到需要撰寫自己的類別(Class), 而當遇到較為複雜的類別時,在撰寫建構子或再給予初值時,就很常會遇到有些值需要初值有些則不需要 ,又或者是許多的值需要傳入建構子裡,造成程式碼的冗長與難以閱讀。 而這裡就有一個設計模式叫做 建立者模式(Builder Pattern),來解決此問題,有缺點也有優點,我們先看以下的例子: ## 二、以往的寫法 **我們先定義一個個人資料類別:** ```java= class Person{ private final String ID; //身分字字號 private String name; //名稱 private int gender; //性別 private String residentAddress; //戶籍地址 private String mailingAddress; //通訊住址 private String email; //電子郵件 private String phone; //電話 private String mobilePhone; //手機 /** 建構子 1 * @param ID 身分證 * @param name 姓名 * @param gender 性別 */ public Person(String ID, String name, int gender){ this.ID = ID; this.name = name; this.gender = gender; } /** 建構子 2 * @param ID 身分證 * @param name 姓名 * @param gender 性別 * @param residentAddress 戶籍地址 * @param mailingAddress 通訊地址 * @param email 電子郵件 * @param phone 電話 * @param mobilePhone 手機 */ public Person(String ID, String name, int gender, String residentAddress, String mailingAddress, String email, String phone, String mobilePhone){ this(ID, name, gender); this.residentAddress = residentAddress; this.mailingAddress = mailingAddress; this.email = email; this.phone = phone; } } ``` 我們可以從上面的定義看出,Person的建構子已經非常的冗長了。 除此之外,我們裡面有許多選項是選填的,例如電話、電子郵件等等,這些都會造成在建構建構子上有困難, 而這時我們就可以利用 Builder Pattern 來重新設計 Student 的建構子。 根據以上的程式,我們會這樣去建構每個人的資料: ```java= Person personA = new Person("xxxxxxxxx", "小明", 0, "台北市XXXXXXXXX", "台北市XXXXXXXXX", "xxxx@gmail.com", "02-xxxxxx", "09xxxxxx" ); Person personB = new Person("xxxxxxxxx", "小王", 0, "高雄市XXXXXXX", "台中市XXXXXXX", null, "02-xxxxxx", null ); Person personC = new Person("xxxxxxxxx", "小美", 1, "新北市XXXXXXXXXXX", "桃園縣XXXXXXXXXXXX", null, null, null); ``` ## 三、建立 Builder Pattern ### 宣告類別 **我們先來看建立者類別的寫法:** ```java= class PersonBuilder{ private String ID; //身分字字號 private String name; //名稱 private int gender; //性別 private String residentAddress; //戶籍地址 private String mailingAddress; //通訊住址 private String email; //電子郵件 private String phone; //電話 private String mobilePhone; //手機 /** 建構子 1 * @param ID 身分證 * @param name 姓名 */ public PersonBuilder(String ID, String name){ this.ID = ID; this.name = name; } /** 設定電話 */ public PersonBuilder setPhone( String phone ){ this.phone = phone; return this; } /** 性別 */ public PersonBuilder setGender( int gender ){ this.gender = gender; return this; } /** 設定電子郵件 */ public PersonBuilder setEmail( String email ){ this.email = email; return this; } /** 設定戶籍地址 */ public PersonBuilder setResidentAddress( String residentAddress ){ this.residentAddress = residentAddress; return this; } /** 設定通訊地址 */ public PersonBuilder setMailingAddress( String mailingAddress ){ this.mailingAddress = mailingAddress; return this; } /** 與戶籍同地址 */ public PersonBuilder setSameAddress(){ this.mailingAddress = residentAddress; return this; } /** 設定手機 */ public PersonBuilder setMobilePhone( String mobilePhone ){ this.mobilePhone = mobilePhone; return this; } /** 建立 Person 物件 */ public Person build(){ return new Person(ID, name, gender, residentAddress, mailingAddress, email, phone, mobilePhone); } } ``` ### 建立物件 **建立 Person 物件:** ```java= Person personA = new PersonBuilder("xxxxxxxx", "小明") .setGender( 0 ) .setResidentAddress( "台北市XXXXXXXXX" ) .setSameAddress() .setEmail( "xxxx@gmail.com") .setPhone( "02-xxxxxx" ) .setMobilePhone( "09xxxxxx" ) .build(); Person personC = new PersonBuilder("xxxxxxxx", "小美") .setGender( 1 ) .setResidentAddress( "台北市XXXXXXXXX" ) .setMailingAddress( "桃園縣XXXXXXXXXXXX" ) .build(); ``` 以這樣直接看起來,是否更容易閱讀了,能兼具程式的可讀性與彈性 ,這個就是建立者模式的精隨所在。 ## 四、建構者 與 被建構者 兩者結合 不過可以看出來,原本一個 Class 卻拆成兩個 Class ,或許讀者覺得這樣這樣變得麻煩,不過當然也可以將它寫在一起。 **我們看以下的例子:** ### 類別定義 ```java= class Person{ private final String ID; //身分字字號 private String name; //名稱 private int gender; //性別 private String residentAddress; //戶籍地址 private String mailingAddress; //通訊住址 private String email; //電子郵件 private String phone; //電話 private String mobilePhone; //手機 /** 建構子 1 * @param ID 身分證 * @param name 姓名 * @param gender 性別 */ public Person(String ID, String name, int gender){ this.ID = ID; this.name = name; this.gender = gender; } /** 建構子 2 * @param ID 身分證 * @param name 姓名 * @param gender 性別 * @param residentAddress 戶籍地址 * @param mailingAddress 通訊地址 * @param email 電子郵件 * @param phone 電話 * @param mobilePhone 手機 */ private Person(String ID, String name, int gender, String residentAddress, String mailingAddress, String email, String phone, String mobilePhone){ this(ID, name, gender); this.residentAddress = residentAddress; this.mailingAddress = mailingAddress; this.email = email; this.phone = phone; } //★★★★更改處 /** 靜態函式,用來取出建構者 * @param ID 身分證 * @param name 姓名 * @return 回傳建構者 */ final static public PersonBuilder getBuilder(String ID, String name){ return new PersonBuilder(); } //寫在被建構者裡的 Builder Class protected static class PersonBuilder{ private String ID; //身分字字號 private String name; //名稱 private int gender; //性別 private String residentAddress; //戶籍地址 private String mailingAddress; //通訊住址 private String email; //電子郵件 private String phone; //電話 private String mobilePhone; //手機 /** 建構子 1 * @param ID 身分證 * @param name 姓名 */ public PersonBuilder(String ID, String name){ this.ID = ID; this.name = name; } /** 設定電話 */ public PersonBuilder setPhone( String phone ){ this.phone = phone; return this; } /** 性別 */ public PersonBuilder setGender( int gender ){ this.gender = gender; return this; } /** 設定電子郵件 */ public PersonBuilder setEmail( String email ){ this.email = email; return this; } /** 設定戶籍地址 */ public PersonBuilder setResidentAddress( String residentAddress ){ this.residentAddress = residentAddress; return this; } /** 設定通訊地址 */ public PersonBuilder setMailingAddress( String mailingAddress ){ this.mailingAddress = mailingAddress; return this; } /** 與戶籍同地址 */ public PersonBuilder setSameAddress(){ this.mailingAddress = residentAddress; return this; } /** 設定手機 */ public PersonBuilder setMobilePhone( String mobilePhone ){ this.mobilePhone = mobilePhone; return this; } /** 建立 Person 物件 */ public Person build(){ return new Person(ID, name, gender, residentAddress, mailingAddress, email, phone, mobilePhone); } } } ``` ### 建立物件 ```java= Person personA = Person.getBuilder("xxxxxxxx", "小明") .setGender( 0 ) .setResidentAddress( "台北市XXXXXXXXX" ) .setSameAddress() .setEmail( "xxxx@gmail.com") .setPhone( "02-xxxxxx" ) .setMobilePhone( "09xxxxxx" ) .build(); Person personC = Person.getBuilder("xxxxxxxx", "小美") .setGender( 1 ) .setResidentAddress( "台北市XXXXXXXXX" ) .setMailingAddress( "桃園縣XXXXXXXXXXXX" ) .build(); ``` ## 五、結論 根據以上的程式範例,我們可以發現 建立者模式(Builder pattern) 的好處, 但是事實上也是有壞處的,我們可以分為以下幾點 好處: * 可讀性高 * 具有彈性 * 也能在 Setter 裡設定參數檢查 * 能留 final 修飾詞在原本的 class 裡 缺點: * 程式碼變的冗長 * 原本的 class 新增參數時,builder 也要做對應的新增 而在 Java 中的有些 Class 也有用到這種設計模式,例如: * [StringBuilder](https://https://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html) * [StringBuffer](https://https://docs.oracle.com/javase/8/docs/api/java/lang/StringBuffer.html) * [Locale](https://https://docs.oracle.com/javase/7/docs/api/java/util/Locale.Builder.html) * [Calendar](https://https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.Builder.html) 我們在撰寫程式時,從簡單到複雜,而在這之中,當整體的類別變得複雜且龐大時,假如自己在使用或建構物件時 ,時常參數定義錯誤或不完全時,就可以去試試看這個方法,雖然會犧牲一些效率與程式碼空間 ,但是要創造出容易維護且彈性高的軟體這些代價是可以被接受的。畢竟我想大家在除錯時 ,一定非常不喜歡看到一大坨長長的程式碼吧!當自己在填入參數時,早就看得眼花撩亂了,更何況是在除錯的環境下!