Best Practice für die Übergabe vieler Argumente an die Methode?

89

Gelegentlich müssen wir Methoden schreiben, die viele, viele Argumente erhalten, zum Beispiel:

public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
{
}

Wenn ich auf diese Art von Problem stoße, kapsle ich häufig Argumente in eine Karte.

Map<Object,Object> params = new HashMap<Object,Object>();
params.put("objA",ObjA) ;

......

public void doSomething(Map<Object,Object> params)
{
 // extracting params 
 Object objA = (Object)params.get("objA");
 ......
 }

Dies ist keine gute Vorgehensweise. Das Einkapseln von Parametern in eine Karte ist eine reine Verschwendung von Effizienz. Das Gute ist, die saubere Signatur, einfach, andere Parameter mit den wenigsten Änderungen hinzuzufügen. Was ist die beste Vorgehensweise für diese Art von Problem?

Sawyer
quelle

Antworten:

137

In Effective Java , Kapitel 7 (Methoden), Punkt 40 (Signatur der Entwurfsmethode sorgfältig), schreibt Bloch:

Es gibt drei Techniken zum Verkürzen zu langer Parameterlisten:

  • Teilen Sie die Methode in mehrere Methoden auf, für die jeweils nur eine Teilmenge der Parameter erforderlich ist
  • Erstellen Sie Hilfsklassen für eine Gruppe von Parametern (normalerweise statische Elementklassen).
  • Passen Sie das Builder-Muster von der Objektkonstruktion an den Methodenaufruf an.

Für weitere Details empfehle ich Ihnen, das Buch zu kaufen, es lohnt sich wirklich.

JRL
quelle
Was sind "zu lange Parameter"? Wann können wir sagen, dass eine Methode zu viele Parameter hat? Gibt es eine bestimmte Nummer oder einen bestimmten Bereich?
Red M
2
@ RedM Ich habe immer mehr als 3 oder 4 Parameter als "zu lang" angesehen
jtate
1
@jtate ist das eine persönliche Entscheidung oder folgen Sie einem offiziellen Dokument?
Red M
1
@ RedM persönliche Präferenz :)
jtate
2
Mit der dritten Ausgabe von Effective Java ist dies Kapitel 8 (Methoden), Punkt 51
GarethOwen
71

Die Verwendung einer Karte mit magischen String-Schlüsseln ist eine schlechte Idee. Sie verlieren jegliche Überprüfung der Kompilierungszeit und es ist wirklich unklar, welche Parameter erforderlich sind. Sie müssten eine sehr vollständige Dokumentation schreiben, um dies auszugleichen. Wirst du dich in ein paar Wochen daran erinnern, was diese Strings sind, ohne auf den Code zu schauen? Was ist, wenn Sie einen Tippfehler gemacht haben? Den falschen Typ verwenden? Sie werden es erst herausfinden, wenn Sie den Code ausführen.

Verwenden Sie stattdessen ein Modell. Erstellen Sie eine Klasse, die ein Container für alle diese Parameter ist. Auf diese Weise behalten Sie die Typensicherheit von Java. Sie können dieses Objekt auch an andere Methoden weitergeben, in Sammlungen ablegen usw.

Wenn der Parametersatz nicht anderweitig verwendet oder weitergegeben wird, kann ein dediziertes Modell natürlich übertrieben sein. Es ist ein Gleichgewicht zu finden, also benutze den gesunden Menschenverstand.

Tom
quelle
24

Wenn Sie viele optionale Parameter haben, können Sie eine fließende API erstellen: Ersetzen Sie eine einzelne Methode durch die Methodenkette

exportWithParams().datesBetween(date1,date2)
                  .format("xml")
                  .columns("id","name","phone")
                  .table("angry_robots")
                  .invoke();

Mit dem statischen Import können Sie innere fließende APIs erstellen:

... .datesBetween(from(date1).to(date2)) ...
Ha.
quelle
2
Was ist, wenn alle Parameter erforderlich sind, nicht optional?
Smaragdhieu
1
Auf diese Weise können Sie auch Standardparameter festlegen. Das Builder-Muster bezieht sich auch auf fließende Schnittstellen. Das sollte wirklich die Antwort sein, denke ich. Abgesehen davon, dass ein langer Konstruktor in kleinere Initialisierungsmethoden unterteilt wird, die optional sind.
Ehtesh Choudhury
13

Es heißt "Introduce Parameter Object". Wenn Sie feststellen, dass Sie dieselbe Parameterliste an mehreren Stellen übergeben, erstellen Sie einfach eine Klasse, die alle enthält.

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);

Selbst wenn Sie nicht so oft dieselbe Parameterliste übergeben, verbessert dieses einfache Refactoring Ihre Lesbarkeit des Codes, was immer gut ist. Wenn Sie sich Ihren Code 3 Monate später ansehen, ist es einfacher zu verstehen, wann Sie einen Fehler beheben oder eine Funktion hinzufügen müssen.

Es ist natürlich eine allgemeine Philosophie, und da Sie keine Details angegeben haben, kann ich Ihnen auch keine detaillierteren Ratschläge geben. :-)

dimitarvp
quelle
Wird die Speicherbereinigung ein Problem sein?
Rupinderjeet
Nicht, wenn Sie das Parameterobjekt in der Aufruferfunktion als lokalen Bereich festlegen und es nicht mutieren. Unter solchen Umständen wird es höchstwahrscheinlich gesammelt und sein Speicher ziemlich schnell wiederverwendet.
dimitarvp
Imo, sollten Sie auch eine zur XXXParameter param = new XXXParameter();Verfügung haben, und dann verwenden XXXParameter.setObjA(objA); etc ...
Satibel
10

Zuerst würde ich versuchen, die Methode umzugestalten. Wenn so viele Parameter verwendet werden, kann es in irgendeiner Weise zu lang sein. Eine Aufschlüsselung würde sowohl den Code verbessern als auch möglicherweise die Anzahl der Parameter für jede Methode verringern. Möglicherweise können Sie auch den gesamten Vorgang in eine eigene Klasse umgestalten. Zweitens würde ich nach anderen Fällen suchen, in denen ich dasselbe (oder dieselbe Obermenge) derselben Parameterliste verwende. Wenn Sie mehrere Instanzen haben, signalisiert dies wahrscheinlich, dass diese Eigenschaften zusammengehören. Erstellen Sie in diesem Fall eine Klasse, die die Parameter enthält, und verwenden Sie sie. Zuletzt würde ich bewerten, ob es sich aufgrund der Anzahl der Parameter lohnt, ein Kartenobjekt zu erstellen, um die Lesbarkeit des Codes zu verbessern. Ich denke, dies ist ein persönlicher Anruf - diese Lösung ist in jeder Hinsicht schmerzhaft und der Kompromisspunkt kann unterschiedlich sein. Für sechs Parameter würde ich es wahrscheinlich nicht tun. Für 10 würde ich wahrscheinlich (wenn keine der anderen Methoden zuerst funktioniert).

Tvanfosson
quelle
8

Dies ist häufig ein Problem beim Erstellen von Objekten.

In diesem Fall verwenden Sie das Builder-Objektmuster . Es funktioniert gut, wenn Sie eine große Liste von Parametern haben und nicht immer alle benötigen.

Sie können es auch an den Methodenaufruf anpassen.

Es erhöht auch die Lesbarkeit erheblich.

public class BigObject
{
  // public getters
  // private setters

  public static class Buider
  {
     private A f1;
     private B f2;
     private C f3;
     private D f4;
     private E f5;

     public Buider setField1(A f1) { this.f1 = f1; return this; }
     public Buider setField2(B f2) { this.f2 = f2; return this; }
     public Buider setField3(C f3) { this.f3 = f3; return this; }
     public Buider setField4(D f4) { this.f4 = f4; return this; }
     public Buider setField5(E f5) { this.f5 = f5; return this; }

    public BigObject build()
    {
      BigObject result = new BigObject();
      result.setField1(f1);
      result.setField2(f2);
      result.setField3(f3);
      result.setField4(f4);
      result.setField5(f5);
      return result;
    }
  }
}

// Usage:
BigObject boo = new BigObject.Builder()
  .setField1(/* whatever */)
  .setField2(/* whatever */)
  .setField3(/* whatever */)
  .setField4(/* whatever */)
  .setField5(/* whatever */)
  .build();

Sie können die Überprüfungslogik auch in die Methoden Builder set .. () und build () einfügen.

Bohdan
quelle
Was würden Sie empfehlen, wenn viele Ihrer Felder sind final? Dies ist die Hauptsache, die mich davon abhält, Hilfsfunktionen zu schreiben. Ich nehme an, ich kann die Felder privat machen und sicherstellen, dass ich sie im Code dieser Klasse nicht falsch ändere, aber ich hoffe auf etwas Eleganteres.
Ragerdl
7

Es gibt ein Muster, das als Parameterobjekt bezeichnet wird .

Die Idee ist, ein Objekt anstelle aller Parameter zu verwenden. Selbst wenn Sie später Parameter hinzufügen müssen, müssen Sie diese nur noch dem Objekt hinzufügen. Die Methodenschnittstelle bleibt gleich.

Padmarag
quelle
5

Sie können eine Klasse erstellen, die diese Daten enthält. Muss zwar aussagekräftig genug sein, aber viel besser als die Verwendung einer Karte (OMG).

Johannes Rudolph
quelle
Ich denke nicht, dass es so notwendig ist, eine Klasse zu erstellen, die einen Methodenparameter enthält.
Sawyer
Ich würde die Klasse nur erstellen, wenn mehrere Instanzen dieselben Parameter übergeben würden. Dies würde signalisieren, dass die Parameter zusammenhängen und wahrscheinlich sowieso zusammengehören. Wenn Sie eine Klasse für eine einzelne Methode erstellen, ist die Heilung wahrscheinlich schlechter als die Krankheit.
Tvanfosson
Ja - Sie können verwandte Parameter in ein DTO oder Wertobjekt verschieben. Sind einige der mehreren Parameter optional, dh die Hauptmethode ist mit diesen zusätzlichen Parametern überladen? In solchen Fällen halte ich es für akzeptabel.
JoseK
Das habe ich damit gemeint, dass es aussagekräftig genug sein muss.
Johannes Rudolph
4

Code Complete * schlägt einige Dinge vor:

  • "Begrenzen Sie die Anzahl der Parameter einer Routine auf etwa sieben. Sieben ist eine magische Zahl für das Verständnis der Menschen" (S. 108).
  • "Setzen Sie die Parameter in die Reihenfolge Eingabe-Änderung-Ausgabe ... Wenn mehrere Routinen ähnliche Parameter verwenden, setzen Sie die ähnlichen Parameter in eine konsistente Reihenfolge" (S. 105).
  • Setzen Sie Status- oder Fehlervariablen zuletzt.
  • Übergeben Sie , wie von tvanfosson erwähnt, nur die Teile einer strukturierten Variablen (Objekte), die die Routine benötigt. Wenn Sie jedoch den größten Teil der strukturierten Variablen in der Funktion verwenden, übergeben Sie einfach die gesamte Struktur. Beachten Sie jedoch, dass dies die Kopplung bis zu einem gewissen Grad fördert.

* Erste Ausgabe, ich weiß, ich sollte aktualisieren. Es ist auch wahrscheinlich, dass sich einige dieser Ratschläge geändert haben, seit die zweite Ausgabe geschrieben wurde, als OOP immer beliebter wurde.

Justin Johnson
quelle
2

Gute Praxis wäre es, umzugestalten. Was bedeutet mit diesen Objekten, dass sie an diese Methode übergeben werden sollten? Sollten sie in ein einzelnes Objekt eingekapselt werden?

GaryF
quelle
ja sie sollten. Beispielsweise weist ein großes Suchformular viele nicht zusammenhängende Einschränkungen und Paginierungsbedürfnisse auf. Sie müssen currentPageNumber, searchCriteria, pageSize ...
Sawyer
2

Die Verwendung einer Karte ist eine einfache Möglichkeit, die Anrufsignatur zu bereinigen, aber dann haben Sie ein anderes Problem. Sie müssen in den Hauptteil der Methode schauen, um zu sehen, was die Methode in dieser Map erwartet, wie die Schlüsselnamen lauten oder welche Typen die Werte haben.

Ein sauberer Weg wäre, alle Parameter in einer Objekt-Bean zu gruppieren, aber das behebt das Problem immer noch nicht vollständig.

Was Sie hier haben, ist ein Designproblem. Mit mehr als 7 Parametern für eine Methode werden Sie Probleme haben, sich daran zu erinnern, was sie darstellen und in welcher Reihenfolge sie sind. Von hier aus erhalten Sie viele Fehler, wenn Sie die Methode in der falschen Parameterreihenfolge aufrufen.

Sie benötigen ein besseres Design der App, keine bewährte Methode, um viele Parameter zu senden.


quelle
1

Erstellen Sie eine Bean-Klasse, legen Sie alle Parameter fest (Setter-Methode) und übergeben Sie dieses Bean-Objekt an die Methode.

Tony
quelle
1
  • Sehen Sie sich Ihren Code an und sehen Sie, warum all diese Parameter übergeben werden. Manchmal ist es möglich, die Methode selbst umzugestalten.

  • Durch die Verwendung einer Karte ist Ihre Methode anfällig. Was ist, wenn jemand, der Ihre Methode verwendet, einen Parameternamen falsch schreibt oder eine Zeichenfolge veröffentlicht, in der Ihre Methode eine UDT erwartet?

  • Definieren Sie ein Übertragungsobjekt . Sie erhalten zumindest eine Typprüfung. Möglicherweise können Sie sogar eine Validierung am Verwendungsort durchführen, anstatt innerhalb Ihrer Methode.

Jeder
quelle
0

Dies ist oft ein Hinweis darauf, dass Ihre Klasse mehr als eine Verantwortung trägt (dh Ihre Klasse tut ZU viel).

Siehe das Prinzip der Einzelverantwortung

für weitere Details.

Hilfsmethode
quelle
0

Wenn Sie zu viele Parameter übergeben, versuchen Sie, die Methode umzugestalten. Vielleicht macht es eine Menge Dinge, die es nicht tun soll. Ist dies nicht der Fall, ersetzen Sie die Parameter durch eine einzelne Klasse. Auf diese Weise können Sie alles in einer einzelnen Klasseninstanz kapseln und die Instanz und nicht die Parameter weitergeben.

Azamsharp
quelle
0

Ich würde sagen, bleib bei der Art und Weise, wie du es vorher getan hast. Die Anzahl der Parameter in Ihrem Beispiel ist nicht viel, aber die Alternativen sind viel schrecklicher.

  1. Karte - Es gibt die Effizienzsache, die Sie erwähnt haben, aber das größere Problem hier sind:

    • Anrufer wissen nicht, was sie Ihnen senden sollen, ohne auf etwas
      anderes zu verweisen ... Haben Sie Javadocs, in denen genau angegeben ist, welche Schlüssel und
      Werte verwendet werden? Wenn Sie dies tun (was großartig ist), ist es auch kein Problem, viele Parameter zu haben.
    • Es wird sehr schwierig, verschiedene Argumenttypen zu akzeptieren. Sie können Eingabeparameter entweder auf einen einzelnen Typ beschränken oder Map <String, Object> verwenden und alle Werte umwandeln. Beide Optionen sind die meiste Zeit schrecklich.
  2. Wrapper-Objekte - dies verschiebt nur das Problem, da Sie das Wrapper-Objekt zuerst füllen müssen - anstatt direkt zu Ihrer Methode, wird es zum Konstruktor des Parameterobjekts. Ob das Verschieben des Problems angemessen ist oder nicht, hängt von der Wiederverwendung des Objekts ab. Zum Beispiel:

Würde es nicht verwenden: Es würde nur einmal beim ersten Aufruf verwendet werden, also viel zusätzlicher Code für 1 Zeile ...?

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    SomeObject i = obj2.callAnotherMethod(a, b, c, h);
    FinalResult j = obj3.callAFinalMethod(c, e, f, h, i);
}

Darf es benutzen: Hier kann es ein bisschen mehr. Erstens können die Parameter für 3 Methodenaufrufe berücksichtigt werden. es kann auch 2 andere Zeilen an sich ausführen ... so wird es in gewissem Sinne zu einer Zustandsvariablen ...

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    e = h.resultOfSomeTransformation();
    SomeObject i = obj2.callAnotherMethod(a, b, c, d, e, f, g);
    f = i.somethingElse();
    FinalResult j = obj3.callAFinalMethod(a, b, c, d, e, f, g, h, i);
}
  1. Builder-Muster - Dies ist aus meiner Sicht ein Anti-Muster. Der wünschenswerteste Mechanismus zur Fehlerbehandlung besteht darin, früher und nicht später zu erkennen. Mit dem Builder-Muster werden Aufrufe mit fehlenden (vom Programmierer nicht berücksichtigt) obligatorischen Parametern von der Kompilierungszeit zur Laufzeit verschoben. Wenn der Programmierer absichtlich null oder ähnliches in den Slot einfügt, ist dies natürlich Laufzeit, aber einige Fehler früher zu erkennen, ist ein viel größerer Vorteil für Programmierer, die sich weigern, die Parameternamen der von ihnen aufgerufenen Methode zu überprüfen. Ich finde es nur angemessen, wenn es um eine große Anzahl optionaler Parameter geht, und selbst dann ist der Nutzen bestenfalls marginal. Ich bin sehr gegen das "Muster" des Bauherrn.

Das andere, was die Leute vergessen zu berücksichtigen, ist die Rolle der IDE bei all dem. Wenn Methoden Parameter haben, generieren IDEs den größten Teil des Codes für Sie, und die roten Linien erinnern Sie daran, was Sie bereitstellen / festlegen müssen. Wenn Sie Option 3 verwenden, verlieren Sie diese vollständig. Jetzt ist es an dem Programmierer, es richtig zu machen, und es gibt keine Hinweise während der Codierungs- und Kompilierungszeit ... der Programmierer muss es testen, um es herauszufinden.

Darüber hinaus haben die Optionen 2 und 3, wenn sie unnötig weit verbreitet sind, aufgrund der großen Menge an doppeltem Code, die sie generieren, langfristige negative Auswirkungen auf die Wartung. Je mehr Code vorhanden ist, desto mehr muss gewartet werden, desto mehr Zeit und Geld wird für die Wartung aufgewendet.

John
quelle