Zu viele Parameter in Java-Methoden, Teil 2: Parameterobjekt

In meinem vorherigen Beitrag habe ich einige der Probleme untersucht, die mit langen Parameterlisten für Methoden und Konstruktoren verbunden sind. In diesem Beitrag habe ich das Ersetzen von Grundelementen und integrierten Typen durch benutzerdefinierte Typen erörtert, um die Lesbarkeit und die Typensicherheit zu verbessern. Dadurch wurden die zahlreichen Parameter einer Methode oder eines Konstruktors besser lesbar, die Anzahl der Parameter wurde jedoch nicht verringert. In diesem Beitrag beschäftige ich mich mit der Verwendung eines Parameterobjekts , um die Anzahl der Parameter auf eine Methode oder einen Konstruktor zu reduzieren.

Es ist im Allgemeinen keine gute Idee, "Junk Drawer" -Objekte zu erstellen, die nicht verwandte Parameter koppeln , deren einzige Beziehung zueinander darin besteht, dass sie an dieselbe Methode oder denselben Konstruktor übergeben werden müssen. Wenn jedoch verwandte Parameter als Teil eines stark zusammenhängenden Objekts an einen Konstruktor oder eine Methode übergeben werden, ist das als Introduce Parameter Object bekannte Refactoring eine gute Lösung. Die Verwendung dieses Refactorings wird als "Gruppierung von Parametern, die natürlich zusammenpassen" beschrieben. Ich werde dieses Refactoring in diesem Beitrag demonstrieren.

Um den Nutzen der Einführung Parameter Objekt Refactoring, lassen Sie uns zunächst einen Blick auf das Beispiel aus dem letzten Beitrag zu zeigen , welche anhand zahlreicher Stringund booleanParameter in einem Methodenaufruf.

 /** * Instantiate a Person object. * * @param lastName * @param firstName * @param middleName * @param salutation * @param suffix * @param streetAddress * @param city * @param state * @param isFemale * @param isEmployed * @param isHomeOwner * @return */ public Person createPerson( final String lastName, final String firstName, final String middleName, final String salutation, final String suffix, final String streetAddress, final String city, final String state, final boolean isFemale, final boolean isEmployed, final boolean isHomeOwner) { // implementation goes here } 

Wie ich im vorherigen Beitrag besprochen habe, ist dieser Ansatz für Anrufer mühsam, macht es allzu einfach, Parameter mit geringer Typensicherheit in der falschen Reihenfolge zu übergeben, und kann die Lesbarkeit des Codes beeinträchtigen. Glücklicherweise bieten die Parameter in diesem Beispiel einige gute Möglichkeiten, das Refactoring "Parameterobjekt einführen" anzuwenden. Die "Namen" -Parameter (einschließlich Anrede und Suffix) können in einer einzelnen vollständigen Namensklasse enthalten sein. Die Adressparameter (Straße, Stadt und Bundesland) können sich in einem einzelnen Adressobjekt befinden. Die anderen Parameter lassen sich möglicherweise nicht so einfach in eine einzelne Klasse mit hoher Kohäsion einteilen.

Mit den vorgeschlagenen Anwendungen des Refactorings "Parameterobjekt einführen" ist der zuvor gezeigte Methodenaufruf dank der reduzierten Anzahl von Parametern einfacher. Dies wird in der nächsten Codeliste gezeigt.

 public Person createPerson( final FullName fullName, final Address address, final boolean isFemale, final boolean isEmployed, final boolean isHomeOwner) { return new Person(); } 

Das obige Beispiel hat jetzt nur noch fünf Parameter und ist für Clients besser lesbar und einfacher zu verwenden. In typografischer Hinsicht ist dies auch sicherer, da es in diesem Fall nahezu unmöglich ist, Namensfolgen mit Adresszeichenfolgen zu verwechseln. Leider bleiben die drei booleschen Parameter eine Quelle potenzieller Verwirrung und Cloud-Lesbarkeit. Die nächsten Codelisten zeigen mögliche Implementierungen der Klassen FullNameund Address.

FullName.java (einfach)

package dustin.examples; /** * Full name of a person. * * @author Dustin */ public final class FullName { private final String lastName; private final String firstName; private final String middleName; private final String salutation; private final String suffix; public FullName( final String newLastName, final String newFirstName, final String newMiddleName, final String newSalutation, final String newSuffix) { this.lastName = newLastName; this.firstName = newFirstName; this.middleName = newMiddleName; this.salutation = newSalutation; this.suffix = newSuffix; } public String getLastName() { return this.lastName; } public String getFirstName() { return this.firstName; } public String getMiddleName() { return this.middleName; } public String getSalutation() { return this.salutation; } public String getSuffix() { return this.suffix; } @Override public String toString() { return this.salutation + " " + this.firstName + " " + this.middleName + this.lastName + ", " + this.suffix; } } 

Address.java (einfach)

package dustin.examples; /** * Representation of a United States address. * * @author Dustin */ public final class Address { private final String streetAddress; private final String city; private final String state; public Address(final String newStreetAddress, final String newCity, final String newState) { this.streetAddress = newStreetAddress; this.city = newCity; this.state = newState; } public String getStreetAddress() { return this.streetAddress; } public String getCity() { return this.city; } public String getState() { return this.state; } @Override public String toString() { return this.streetAddress + ", " + this.city + ", " + this.state; } } 

Obwohl der Code verbessert wurde, gibt es noch einige Probleme, die verbessert werden können. Insbesondere hat die ursprüngliche Methode mit zu vielen Parametern noch drei booleanParameter, die leicht miteinander verwechselt werden können. Obwohl die StringParameter für diese Methode in zwei neue Klassen eingeteilt wurden, bestehen diese beiden neuen Klassen immer noch aus einer Reihe von Strings. In diesen Fällen möchten Sie möglicherweise das Refactoring von Introduce Parameter Object durch die Verwendung benutzerdefinierter Typen ergänzen . Unter Verwendung der benutzerdefinierten Typen, die ich in meinem letzten Beitrag gezeigt habe, sieht die Methode mit zu vielen Parametern jetzt so aus wie in der nächsten Codeliste.

 public Person createPerson( final FullName fullName, final Address address, final Gender gender, final EmploymentStatus employment, final HomeownerStatus homeownerStatus) { // implementation goes here } 

Die Methode hat jetzt weniger Parameter und die Parameter, die sie hat, sind alle von unterschiedlichen Typen. IDEs und der Java-Compiler können jetzt besonders hilfreich sein, um sicherzustellen, dass Clients diese Schnittstelle ordnungsgemäß verwenden. Das Anwenden von benutzerdefinierten Typen (im letzten Beitrag geschrieben) auf die Klassen FullNameund Addressführt zu den nächsten beiden neuen Codelisten für diese Klassen.

FullName.java (Benutzerdefinierte Typen)

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; public 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; } @Override public String toString() { return this.salutation + " " + this.firstName + " " + this.middleName + this.lastName + ", " + this.suffix; } } 

Address.java (Benutzerdefinierte Typen)

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; public 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; } @Override public String toString() { return this.streetAddress + ", " + this.city + ", " + this.state; } } 

Alle meine Beispiele waren bisher eigenständige publicKlassen. Wenn ich ein Parameterobjekt nur zum Übergeben von Informationen zwischen Methoden und Konstruktoren im selben Paket benötige, kann es häufig hilfreich sein, diese Parameterobjektklassen in den packageGültigkeitsbereich zu integrieren. In einigen Fällen können auch verschachtelte Klassen für diese Parameterobjekte verwendet werden.

Vor- und Nachteile

Der offensichtlichste Vorteil des Parameterobjekts ist die Verringerung der Anzahl der Parameter, die an eine Methode oder einen Konstruktor übergeben werden. Diese Kapselung verwandter Parameter erleichtert die schnelle Feststellung, welche Typen an die Methode oder den Konstruktor übergeben werden. Für einen Entwickler ist es einfacher, eine kleinere Anzahl von Parametern zu verstehen.

Parameterobjekte haben einen der gleichen Vorteile, die benutzerdefinierte Typen bieten: die Möglichkeit, dem Parameterobjekt zusätzliche Verhaltensweisen und Eigenschaften hinzuzufügen, um die Funktionen zu vereinfachen. Wenn Sie beispielsweise eine AddressKlasse anstelle einer Reihe von StringTypen haben, können Sie Adressen überprüfen.

Kosten und Nachteile

Der Hauptnachteil des Parameterobjekts ist ein wenig zusätzliche Arbeit zum Entwerfen, Implementieren und Testen der Klasse. Diese sind jedoch recht einfach zu schreiben und zu testen, und moderne Tools wie IDEs und Skriptsprachen machen es noch einfacher, die alltäglichsten und langwierigsten Teile dieser Aufgaben zu automatisieren. Ein noch kleineres Argument gegen diesen Ansatz ist, dass er missbraucht werden kann. Wenn ein Entwickler damit beginnt, nicht verwandte Parameter zu einer Klasse zusammenzufassen, um die Anzahl der Parameter zu verringern, hilft dies nicht unbedingt. Dieser Ansatz reduziert zwar die Anzahl der Parameter, aber das endgültige Ziel der Verbesserung der Lesbarkeit wird nicht erreicht, und es könnte argumentiert werden, dass dieser Ansatz noch weniger lesbar ist.

Fazit

Parameterobjekte bieten einen guten, sauberen Ansatz zum angemessenen Einkapseln verwandter Parameter, um die Gesamtzahl der Parameter auf eine Methode oder einen Konstruktor zu reduzieren. Sie sind einfach zu implementieren und können die Lesbarkeits- und Typensicherheitsparameter, die an Methoden- und Konstruktoraufrufe übergeben werden, erheblich verbessern. Parameterobjekte können durch die Verwendung benutzerdefinierter Typen weiter verbessert werden, wie in meinem vorherigen Beitrag erläutert.

Originalbeitrag verfügbar unter //marxsoftware.blogspot.com/ (Inspiriert von tatsächlichen Ereignissen)

Diese Geschichte "Zu viele Parameter in Java-Methoden, Teil 2: Parameterobjekt" wurde ursprünglich von JavaWorld veröffentlicht.