Zu viele Parameter in Java-Methoden, Teil 3: Builder-Muster

In meinen beiden unmittelbar vorherigen Beiträgen habe ich versucht, die Anzahl der für einen Konstruktor- oder Methodenaufruf erforderlichen Parameter über benutzerdefinierte Typen und Parameterobjekte zu reduzieren. In diesem Beitrag beschäftige ich mich mit der Verwendung des Builder-Musters, um die Anzahl der für einen Konstruktor erforderlichen Parameter zu verringern, und diskutiere, wie dieses Muster sogar bei Nicht-Konstruktor-Methoden helfen kann, die zu viele Parameter verwenden.

In der zweiten Ausgabe von Effective Java führt Josh Bloch die Verwendung des Builder-Musters in Punkt 2 für den Umgang mit Konstruktoren ein, für die zu viele Parameter erforderlich sind. Bloch demonstriert nicht nur die Verwendung des Builders, sondern erklärt ihm auch die Vorteile gegenüber Konstruktoren, die eine große Anzahl von Parametern akzeptieren. Ich werde am Ende dieses Beitrags auf diese Vorteile eingehen, aber ich denke, es ist wichtig darauf hinzuweisen, dass Bloch dieser Praxis einen ganzen Punkt in seinem Buch gewidmet hat.

Um die Vorteile dieses Ansatzes zu veranschaulichen, verwende ich die folgende Beispielklasse Person. Es enthält nicht alle Methoden, die ich normalerweise zu einer solchen Klasse hinzufügen würde, weil ich mich auf deren Konstruktion konzentrieren möchte.

Person.java (ohne Builder-Muster)

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

Der Konstruktor dieser Klasse funktioniert, aber es ist schwierig, den Clientcode ordnungsgemäß zu verwenden. Das Builder-Muster kann verwendet werden, um die Verwendung des Konstruktors zu vereinfachen. NetBeans wird dies für mich umgestalten, wie ich zuvor geschrieben habe. Als nächstes wird ein Beispiel für den überarbeiteten Code gezeigt (NetBeans erstellt dazu alle neuen Builder-Klassen).

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

Ich bevorzuge es, meinen Builder als verschachtelte Klasse in der Klasse zu haben, deren Objekt er erstellt, aber die automatische Generierung eines eigenständigen Builders durch NetBeans ist sehr einfach zu verwenden. Ein weiterer Unterschied zwischen dem von NetBeans generierten Builder und den Buildern, die ich gerne schreibe, besteht darin, dass für meine bevorzugten Builder-Implementierungen Felder erforderlich sind, die im Konstruktor des Builders bereitgestellt werden, anstatt einen Konstruktor ohne Argumente bereitzustellen. Die nächste Codeliste zeigt meine PersonKlasse von oben mit einem Builder, der als verschachtelte Klasse hinzugefügt wurde.

Person.java mit verschachtelter Person.Builder

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

Der Builder kann noch besser sein, wenn er durch die Verwendung benutzerdefinierter Typen und Parameterobjekte erweitert wird, wie in meinen ersten beiden Beiträgen zum Problem "Zu viele Parameter" beschrieben. Dies wird in der nächsten Codeliste gezeigt.

Person.java mit verschachteltem Builder, benutzerdefinierten Typen und Parameterobjekt

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

Die letzten Codelisten zeigen, wie ein Builder normalerweise verwendet wird - um ein Objekt zu erstellen. In der Tat befindet sich das Element im Builder (Element Nr. 2) in Joshua Blochs zweiter Ausgabe von Effective Java im Kapitel zum Erstellen (und Zerstören) von Objekten. Der Builder kann jedoch indirekt bei Nicht-Konstruktor-Methoden helfen, indem er eine einfachere Möglichkeit zum Erstellen von Parameterobjekten ermöglicht, die an Methoden übergeben werden.