Javaメソッドのパラメーターが多すぎる、パート3:ビルダーパターン

直前の2つの投稿では、カスタム型とパラメーターオブジェクトを介して、コンストラクターまたはメソッドの呼び出しに必要なパラメーターの数を減らすことを検討しました。この投稿では、ビルダーパターンを使用してコンストラクターに必要なパラメーターの数を減らし、このパターンがパラメーターを取りすぎる非コンストラクターメソッドでどのように役立つかについて説明します。

効果的なJavaの第2版では、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を、オブジェクトを構築するクラス内のネストされたクラスとして使用することを好みますが、スタンドアロンBuilderのNetBeans自動生成は非常に使いやすいです。NetBeansで生成されたBuilderと私が書きたいBuilderのもう1つの違いは、私の好みのBuilder実装には、引数なしのコンストラクターではなく、Builderのコンストラクターで提供されるフィールドが必要なことです。次のコードリストは、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); } } } 

「パラメーターが多すぎる」問題に関する最初の2つの投稿で概説したように、カスタムタイプとパラメーターオブジェクトを使用して拡張すると、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); } } } 

最後の2つのコードリストは、オブジェクトを構築するためにBuilderが通常どのように使用されるかを示しています。実際、JoshuaBlochのEffectiveJavaの第2版にあるビルダーの項目(項目#2)は、オブジェクトの作成(および破棄)に関する章にあります。ただし、ビルダーは、メソッドに渡されるパラメーターオブジェクトをより簡単に構築できるようにすることで、コンストラクター以外のメソッドを間接的に支援できます。