Java方法中的参数太多,第3部分:构建器模式
在我的前兩篇文章中,我研究了如何通過自定義類型和參數對象減少構造函數或方法調用所需的參數數量。 在本文中,我將討論如何使用構建器模式來減少構造器所需的參數數量,并討論該模式如何甚至可以幫助采用過多參數的非構造器方法。
在《 有效Java第二版》中, 喬什·布洛赫 ( Josh Bloch) 在項目#2中引入了使用構建器模式來處理需要太多參數的構造器。 Bloch不僅演示了如何使用Builder,而且還解釋了它比接受大量參數的構造函數更具優勢。 我將在本文結尾處介紹這些優點,但認為必須指出,Bloch在他的書中將整個項目專門用于這種做法。
為了說明這種方法的優點,我將使用以下示例Person類。 它沒有我通常會添加到此類的所有方法,因為我想專注于其構造。
Person.java(無構建器模式)
package dustin.examples;/*** Person class used as part of too many parameters demonstration.* * @author Dustin*/ public class Person {private final String lastName;private final String firstName;private final String middleName;private final String salutation;private final String suffix;private final String streetAddress;private final String city;private final String state;private final boolean isFemale;private final boolean isEmployed;private final boolean isHomewOwner;public Person(final String newLastName,final String newFirstName,final String newMiddleName,final String newSalutation,final String newSuffix,final String newStreetAddress,final String newCity,final String newState,final boolean newIsFemale,final boolean newIsEmployed,final boolean newIsHomeOwner){this.lastName = newLastName;this.firstName = newFirstName;this.middleName = newMiddleName;this.salutation = newSalutation;this.suffix = newSuffix;this.streetAddress = newStreetAddress;this.city = newCity;this.state = newState;this.isFemale = newIsFemale;this.isEmployed = newIsEmployed;this.isHomewOwner = newIsHomeOwner;} }此類的構造函數有效,但是客戶端代碼難以正確使用。 可以使用Builder模式使構造函數更易于使用。 正如我之前所寫的那樣, NetBeans將為我重構此內容 。 接下來顯示重構代碼的示例(NetBeans通過創建所有新的Builder類來完成此操作)。
PersonBuilder.java
package dustin.examples;public class PersonBuilder {private String newLastName;private String newFirstName;private String newMiddleName;private String newSalutation;private String newSuffix;private String newStreetAddress;private String newCity;private String newState;private boolean newIsFemale;private boolean newIsEmployed;private boolean newIsHomeOwner;public PersonBuilder(){}public PersonBuilder setNewLastName(String newLastName) {this.newLastName = newLastName;return this;}public PersonBuilder setNewFirstName(String newFirstName) {this.newFirstName = newFirstName;return this;}public PersonBuilder setNewMiddleName(String newMiddleName) {this.newMiddleName = newMiddleName;return this;}public PersonBuilder setNewSalutation(String newSalutation) {this.newSalutation = newSalutation;return this;}public PersonBuilder setNewSuffix(String newSuffix) {this.newSuffix = newSuffix;return this;}public PersonBuilder setNewStreetAddress(String newStreetAddress) {this.newStreetAddress = newStreetAddress;return this;}public PersonBuilder setNewCity(String newCity) {this.newCity = newCity;return this;}public PersonBuilder setNewState(String newState) {this.newState = newState;return this;}public PersonBuilder setNewIsFemale(boolean newIsFemale) {this.newIsFemale = newIsFemale;return this;}public PersonBuilder setNewIsEmployed(boolean newIsEmployed) {this.newIsEmployed = newIsEmployed;return this;}public PersonBuilder setNewIsHomeOwner(boolean newIsHomeOwner) {this.newIsHomeOwner = newIsHomeOwner;return this;}public Person createPerson() {return new Person(newLastName, newFirstName, newMiddleName, newSalutation, newSuffix, newStreetAddress, newCity, newState, newIsFemale, newIsEmployed, newIsHomeOwner);}}我更喜歡將Builder作為嵌套類在其要構建的對象中,但是NetBeans自動生成獨立的Builder非常易于使用。 NetBeans生成的生成器和我要編寫的生成器之間的另一個區別是,我首選的生成器實現在生成器的構造函數中提供了必填字段,而不是提供了無參數的構造函數。 下一個代碼清單從上方顯示了我的Person類,并在其中添加了一個Builder作為嵌套類。
具有嵌套Person.Builder的Person.java
package dustin.examples;/*** Person class used as part of too many parameters demonstration.* * @author Dustin*/ public class Person {private final String lastName;private final String firstName;private final String middleName;private final String salutation;private final String suffix;private final String streetAddress;private final String city;private final String state;private final boolean isFemale;private final boolean isEmployed;private final boolean isHomewOwner;public Person(final String newLastName,final String newFirstName,final String newMiddleName,final String newSalutation,final String newSuffix,final String newStreetAddress,final String newCity,final String newState,final boolean newIsFemale,final boolean newIsEmployed,final boolean newIsHomeOwner){this.lastName = newLastName;this.firstName = newFirstName;this.middleName = newMiddleName;this.salutation = newSalutation;this.suffix = newSuffix;this.streetAddress = newStreetAddress;this.city = newCity;this.state = newState;this.isFemale = newIsFemale;this.isEmployed = newIsEmployed;this.isHomewOwner = newIsHomeOwner;}public static class PersonBuilder{private String nestedLastName;private String nestedFirstName;private String nestedMiddleName;private String nestedSalutation;private String nestedSuffix;private String nestedStreetAddress;private String nestedCity;private String nestedState;private boolean nestedIsFemale;private boolean nestedIsEmployed;private boolean nestedIsHomeOwner;public PersonBuilder(final String newFirstName,final String newCity,final String newState) {this.nestedFirstName = newFirstName;this.nestedCity = newCity;this.nestedState = newState;}public PersonBuilder lastName(String newLastName){this.nestedLastName = newLastName;return this;}public PersonBuilder firstName(String newFirstName){this.nestedFirstName = newFirstName;return this;}public PersonBuilder middleName(String newMiddleName){this.nestedMiddleName = newMiddleName;return this;}public PersonBuilder salutation(String newSalutation){this.nestedSalutation = newSalutation;return this;}public PersonBuilder suffix(String newSuffix){this.nestedSuffix = newSuffix;return this;}public PersonBuilder streetAddress(String newStreetAddress){this.nestedStreetAddress = newStreetAddress;return this;}public PersonBuilder city(String newCity){this.nestedCity = newCity;return this;}public PersonBuilder state(String newState){this.nestedState = newState;return this;}public PersonBuilder isFemale(boolean newIsFemale){this.nestedIsFemale = newIsFemale;return this;}public PersonBuilder isEmployed(boolean newIsEmployed){this.nestedIsEmployed = newIsEmployed;return this;}public PersonBuilder isHomeOwner(boolean newIsHomeOwner){this.nestedIsHomeOwner = newIsHomeOwner;return this;}public Person createPerson(){return new Person(nestedLastName, nestedFirstName, nestedMiddleName,nestedSalutation, nestedSuffix,nestedStreetAddress, nestedCity, nestedState,nestedIsFemale, nestedIsEmployed, nestedIsHomeOwner);}} }當通過使用自定義類型和參數對象進行增強時,Builder甚至會更好,如我在“過多參數”問題的前兩篇文章中概述的那樣。 這顯示在下一個代碼清單中。
具有嵌套生成器,自定義類型和參數對象的Person.java
package dustin.examples;/*** Person class used as part of too many parameters demonstration.* * @author Dustin*/ public class Person {private final FullName name;private final Address address;private final Gender gender;private final EmploymentStatus employment;private final HomeownerStatus homeOwnerStatus;/*** Parameterized constructor can be private because only my internal builder* needs to call me to provide an instance to clients.* * @param newName Name of this person.* @param newAddress Address of this person.* @param newGender Gender of this person.* @param newEmployment Employment status of this person.* @param newHomeOwner Home ownership status of this person.*/private Person(final FullName newName, final Address newAddress,final Gender newGender, final EmploymentStatus newEmployment,final HomeownerStatus newHomeOwner){this.name = newName;this.address = newAddress;this.gender = newGender;this.employment = newEmployment;this.homeOwnerStatus = newHomeOwner;}public FullName getName(){return this.name;}public Address getAddress(){return this.address;}public Gender getGender(){return this.gender;}public EmploymentStatus getEmployment(){return this.employment;}public HomeownerStatus getHomeOwnerStatus(){return this.homeOwnerStatus;}/*** Builder class as outlined in the Second Edition of Joshua Bloch's* Effective Java that is used to build a {@link Person} instance.*/public static class PersonBuilder{private FullName nestedName;private Address nestedAddress;private Gender nestedGender;private EmploymentStatus nestedEmploymentStatus;private HomeownerStatus nestedHomeOwnerStatus;public PersonBuilder(final FullName newFullName,final Address newAddress) {this.nestedName = newFullName;this.nestedAddress = newAddress;}public PersonBuilder name(final FullName newName){this.nestedName = newName;return this;}public PersonBuilder address(final Address newAddress){this.nestedAddress = newAddress;return this;}public PersonBuilder gender(final Gender newGender){this.nestedGender = newGender;return this;}public PersonBuilder employment(final EmploymentStatus newEmploymentStatus){this.nestedEmploymentStatus = newEmploymentStatus;return this;}public PersonBuilder homeOwner(final HomeownerStatus newHomeOwnerStatus){this.nestedHomeOwnerStatus = newHomeOwnerStatus;return this;}public Person createPerson(){return new Person(nestedName, nestedAddress, nestedGender,nestedEmploymentStatus, nestedHomeOwnerStatus);}} }最后兩對代碼清單顯示了通常如何使用Builder來構造對象。 實際上, 約書亞·布洛赫 ( Joshua Bloch)的 《 有效Java 》 第二版中關于構建器的項目(項目2)在創建(和銷毀)對象的章節中。 但是,構建器可以通過允許更簡單的方法來構建傳遞給方法的參數對象,從而間接地幫助非構造方法。
例如,在最后一個代碼清單中,方法已將一些參數對象( FullName和Address )傳遞給它們。 客戶必須構造這些參數對象可能很乏味,并且可以使用構建器來使該過程減少繁瑣。 因此,盡管在每種情況下都使用構建器進行構造,但它允許更輕松地使用減少方法自變量計數的參數對象,從而間接地使非構造器方法受益。
接下來顯示將用作參數對象的FullName和Address類的新定義,以及使用Builder本身。
帶有Builder的FullName.java
package dustin.examples;/*** Full name of a person.* * @author Dustin*/ public final class FullName {private final Name lastName;private final Name firstName;private final Name middleName;private final Salutation salutation;private final Suffix suffix;private FullName(final Name newLastName,final Name newFirstName,final Name newMiddleName,final Salutation newSalutation,final Suffix newSuffix){this.lastName = newLastName;this.firstName = newFirstName;this.middleName = newMiddleName;this.salutation = newSalutation;this.suffix = newSuffix;}public Name getLastName(){return this.lastName;}public Name getFirstName(){return this.firstName;}public Name getMiddleName(){return this.middleName;}public Salutation getSalutation(){return this.salutation;}public Suffix getSuffix(){return this.suffix;}@Overridepublic String toString(){return this.salutation + " " + this.firstName + " " + this.middleName+ this.lastName + ", " + this.suffix;}public static class FullNameBuilder{private final Name nestedLastName;private final Name nestedFirstName;private Name nestedMiddleName;private Salutation nestedSalutation;private Suffix nestedSuffix;public FullNameBuilder(final Name newLastName, final Name newFirstName){this.nestedLastName = newLastName;this.nestedFirstName = newFirstName;}public FullNameBuilder middleName(final Name newMiddleName){this.nestedMiddleName = newMiddleName;return this;}public FullNameBuilder salutation(final Salutation newSalutation){this.nestedSalutation = newSalutation;return this;}public FullNameBuilder suffix(final Suffix newSuffix){this.nestedSuffix = newSuffix;return this;}public FullName createFullName(){return new FullName(nestedLastName, nestedFirstName, nestedMiddleName,nestedSalutation, nestedSuffix);}} }使用Builder的Address.java
package dustin.examples;/*** Representation of a United States address.* * @author Dustin*/ public final class Address {private final StreetAddress streetAddress;private final City city;private final State state;private Address(final StreetAddress newStreetAddress, final City newCity, final State newState){this.streetAddress = newStreetAddress;this.city = newCity;this.state = newState;}public StreetAddress getStreetAddress(){return this.streetAddress;}public City getCity(){return this.city;}public State getState(){return this.state;}@Overridepublic String toString(){return this.streetAddress + ", " + this.city + ", " + this.state;}public static class AddressBuilder{private StreetAddress nestedStreetAddress;private final City nestedCity;private final State nestedState;public AddressBuilder(final City newCity, final State newState){this.nestedCity = newCity;this.nestedState = newState;}public AddressBuilder streetAddress(final StreetAddress newStreetAddress){this.nestedStreetAddress = newStreetAddress;return this;}public Address createAddress(){return new Address(nestedStreetAddress, nestedCity, nestedState);}} }通過將上述構建器包含在類中,可以創建一個Person實例,如下面的代碼清單所示。 之后顯示一個更傳統的Person實例實例以進行比較。
客戶端代碼實例化的兩個實例的Person與建筑商
final Person person1 = new Person.PersonBuilder(new FullName.FullNameBuilder(new Name("Dynamite"), new Name("Napoleon")).createFullName(),new Address.AddressBuilder(new City("Preston"), State.ID).createAddress()).createPerson();final Person person2 = new Person.PersonBuilder(new FullName.FullNameBuilder(new Name("Coltrane"), new Name("Rosco")).middleName(new Name("Purvis")).createFullName(),new Address.AddressBuilder(new City("Hazzard"), State.GA).createAddress()).gender(Gender.MALE).employment(EmploymentStatus.EMPLOYED).createPerson();實例化沒有構建器的人
final person = new Person("Coltrane", "Rosco", "Purvis", null, "Hazzard", "Georgia", false, true, true);如先前的代碼片段所示,與使用構建器類相比,用于調用傳統Java構造器的客戶端代碼更不易讀,而且更容易搞亂。 相同類型(字符串和布爾值)的種類繁多,以及在構造函數中對可選屬性的調用中放置null的必要性,為這種方法提供了許多方法,使它們以糟糕的結果告終。
優勢與優勢
構建器模式的成本很高,因為每個模式本質上必須將代碼行數加倍,并且用于設置這些屬性。 但是,當客戶代碼在可用性和可讀性方面大大受益時,這個代價是值得的。 構造函數的參數會減少,并以高度可讀的方法調用提供。
Builder方法的另一個優點是能夠通過使用“ set”方法在單個語句和狀態下獲取對象而不會出現多個狀態下的對象問題。 我越來越意識到在多核世界中不變性的價值,當該類具有大量屬性時, Builder模式非常適合該不變類。 我也喜歡不需要為可選參數傳遞null到構造函數。
Builder模式不僅使代碼更具可讀性,而且使應用IDE的代碼完成功能更加容易。 在有效Java第二版的第2條中概述了與構建器一起使用時,構建器模式的其他優點。
成本與劣勢
如上所示和上面提到的,對于使用setbuilder方法的 “ set”方法,給定類的代碼行數必須實質上增加一倍。 此外,盡管客戶代碼更具可讀性,但客戶代碼也更為冗長。 我認為,隨著參數數量的增加或更多參數共享同一類型,或者隨著可選參數數量的增加,更高的可讀性值得付出代價。
帶有構建器的類中的代碼行有時意味著開發人員在將屬性添加到主類時可能會忘記為構建器添加對新屬性的支持。 為了嘗試解決這個問題,我想將構建器嵌套在他們構建的類中,這樣對于開發人員來說,很明顯,有一個相關的構建器需要進行類似的更新。 盡管仍然存在開發人員忘記為構建器添加對新屬性的支持的風險,但這與忘記為類的toString() , equals(Object) , hashCode()添加新屬性的風險沒有什么不同。或通常基于類的所有屬性的其他方法。
在構建器的實現中,我使客戶端將必需的屬性傳遞到了構建器的構造函數中,而不是通過“ set”方法。 這樣做的好處是,在開發人員調用(如果曾經調用過)適當的“ set”方法來設置其他字段之前,對象總是以“完成”狀態實例化,而不是處于未完成狀態。 這對于享受不變性的好處是必要的。 但是,該方法的一個次要缺點是,我沒有獲得為要設置的字段命名的方法的可讀性優點。
顧名思義,Builder實際上只是構造函數的替代方案,并未直接用于減少非構造函數方法參數的數量。 但是,可以將構建器與參數對象結合使用,以減少非構造器方法參數的數量。 可以在有關“ 深入研究Builder模式 ” 一文中的評論中找到反對使用Builder進行對象構造的更多論據。
結論
當我有很多參數時,尤其是當其中許多參數為null且其中許多參數共享同一數據類型時,我真的很喜歡使用Builder模式構造對象。 開發人員可能會認為,用于實現Builder的額外代碼可能無法證明其對于少量參數的好處,尤其是在需要少量參數且類型不同的情況下。 在這種情況下,使用傳統的構造函數可能是合乎需要的,或者,如果不希望具有不變性,則使用無參數的構造函數并要求客戶端知道調用必要的“設置”方法可能是合乎需要的。
翻譯自: https://www.javacodegeeks.com/2013/10/too-many-parameters-in-java-methods-part-3-builder-pattern.html
總結
以上是生活随笔為你收集整理的Java方法中的参数太多,第3部分:构建器模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阿里云DDOS(阿里云ddos共计)
- 下一篇: 阿里 ddos(阿里ddos比赛)