在我的前两篇文章中,我研究了如何通过自定义类型和参数对象减少构造函数或方法调用所需的参数数量。 在本文中,我将讨论如何使用构建器模式来减少构造器所需的参数数量,并讨论该模式如何甚至可以帮助采用过多参数的非构造器方法。
在《 有效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