Was ist der beste Weg, um eine Methode mit zu vielen (6+) Parametern umzugestalten?


102

Gelegentlich stoße ich auf Methoden mit einer unangenehmen Anzahl von Parametern. Meistens scheinen sie Konstrukteure zu sein. Es scheint, als sollte es einen besseren Weg geben, aber ich kann nicht sehen, was es ist.

return new Shniz(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)

Ich habe darüber nachgedacht, Strukturen zur Darstellung der Parameterliste zu verwenden, aber das scheint das Problem nur von einem Ort zum anderen zu verschieben und dabei einen anderen Typ zu erstellen.

ShnizArgs args = new ShnizArgs(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
return new Shniz(args);

Das scheint also keine Verbesserung zu sein. Was ist der beste Ansatz?


Sie sagten "struct". Dieser Begriff hat unterschiedliche Konnotationen in verschiedenen Programmiersprachen. Was soll das heißen?
Jay Bazuzi

1
Wenn Sie nach einer bestimmten Sprache suchen, die eindeutig ist, wählen Sie C #. Aber im Grunde nur eine einfache Immobilien-Tasche. Es hat verschiedene benannte Eigenschaften mit verschiedenen Typen. Könnte als Klasse, Hash-Tabelle, Struktur oder was auch immer definiert werden.
rekursiv

Dieser Artikel bietet einige gute Einblicke in das Thema. Javascript-spezifisch, aber die Prinzipien können auf andere Sprachen angewendet werden.
Lala

Antworten:


94

Der beste Weg wäre, Wege zu finden, um die Argumente zusammenzufassen. Dies setzt voraus und funktioniert wirklich nur, wenn Sie am Ende mehrere "Gruppierungen" von Argumenten haben würden.

Wenn Sie beispielsweise die Spezifikation für ein Rechteck übergeben, können Sie x, y, Breite und Höhe übergeben, oder Sie können einfach ein Rechteckobjekt übergeben, das x, y, Breite und Höhe enthält.

Achten Sie beim Refactoring auf solche Dinge, um sie etwas aufzuräumen. Wenn die Argumente wirklich nicht kombiniert werden können, prüfen Sie, ob Sie gegen das Prinzip der Einzelverantwortung verstoßen.


4
Gute Idee, aber schlechtes Beispiel; Der Konstruktor für das Rechteck müsste 4 Argumente haben. Dies wäre sinnvoller, wenn die Methode 2 Sätze von Rechteckkoordinaten / -dimensionen erwarten würde. Dann könnten Sie 2 Rechtecke anstelle von x1, x2, y1, y2 übergeben ...
Outlaw Programmer

2
Meinetwegen. Wie gesagt, es macht wirklich nur Sinn, wenn Sie am Ende mehrere logische Gruppierungen haben.
Matthew Brubaker

23
+1: Für die Einzelverantwortung ist dies einer der wenigen Kommentare in allen Antworten, die sich wirklich mit dem eigentlichen Problem befassen. Welches Objekt benötigt wirklich 7 unabhängige Werte, um seine Identität zu bilden.
AnthonyWJones

6
@AnthonyWJones Ich bin anderer Meinung. Daten für die aktuelle Wetterlage können viel mehr unabhängige Werte haben, um ihre Identität zu bilden.
Funktion7

107

Ich gehe davon aus, dass du C # meinst . Einige dieser Dinge gelten auch für andere Sprachen.

Sie haben mehrere Möglichkeiten:

Wechseln Sie vom Konstruktor zum Eigenschaftssetter . Dies kann die Lesbarkeit des Codes verbessern, da dem Leser klar ist, welcher Wert welchen Parametern entspricht. Die Syntax des Objektinitialisierers lässt dies gut aussehen. Es ist auch einfach zu implementieren, da Sie einfach automatisch generierte Eigenschaften verwenden und das Schreiben der Konstruktoren überspringen können.

class C
{
    public string S { get; set; }
    public int I { get; set; }
}

new C { S = "hi", I = 3 };

Sie verlieren jedoch die Unveränderlichkeit und die Fähigkeit, sicherzustellen, dass die erforderlichen Werte festgelegt sind, bevor Sie das Objekt zur Kompilierungszeit verwenden.

Builder-Muster .

Denken Sie an die Beziehung zwischen stringund StringBuilder. Sie können dies für Ihre eigenen Klassen bekommen. Ich implementiere es gerne als verschachtelte Klasse, daher hat die Klasse Ceine verwandte Klasse C.Builder. Ich mag auch eine fließende Oberfläche des Builders. Wenn Sie es richtig machen, können Sie die folgende Syntax erhalten:

C c = new C.Builder()
    .SetX(4)    // SetX is the fluent equivalent to a property setter
    .SetY("hello")
    .ToC();     // ToC is the builder pattern analog to ToString()

// Modify without breaking immutability
c = c.ToBuilder().SetX(2).ToC();

// Still useful to have a traditional ctor:
c = new C(1, "...");

// And object initializer syntax is still available:
c = new C.Builder { X = 4, Y = "boing" }.ToC();

Ich habe ein PowerShell-Skript, mit dem ich den Builder-Code generieren kann, um all dies zu tun, wobei die Eingabe wie folgt aussieht:

class C {
    field I X
    field string Y
}

So kann ich zur Kompilierungszeit generieren. partialMit Klassen kann ich sowohl die Hauptklasse als auch den Builder erweitern, ohne den generierten Code zu ändern.

Refactoring "Parameterobjekt einführen" . Siehe den Refactoring-Katalog . Die Idee ist, dass Sie einige der Parameter, die Sie übergeben, in einen neuen Typ einfügen und stattdessen eine Instanz dieses Typs übergeben. Wenn Sie dies ohne nachzudenken tun, werden Sie wieder dort landen, wo Sie begonnen haben:

new C(a, b, c, d);

wird

new C(new D(a, b, c, d));

Dieser Ansatz hat jedoch das größte Potenzial, sich positiv auf Ihren Code auszuwirken. Fahren Sie also mit den folgenden Schritten fort:

  1. Suchen Sie nach Teilmengen von Parametern, die zusammen Sinn machen. Nur gedankenlos alle Parameter einer Funktion zusammenzufassen, bringt Ihnen nicht viel. Ziel ist es, sinnvolle Gruppierungen zu haben. Sie werden wissen, dass Sie es richtig verstanden haben, wenn der Name des neuen Typs offensichtlich ist.

  2. Suchen Sie nach anderen Stellen, an denen diese Werte zusammen verwendet werden, und verwenden Sie dort auch den neuen Typ. Wenn Sie einen guten neuen Typ für eine Reihe von Werten gefunden haben, die Sie bereits überall verwenden, ist dieser neue Typ wahrscheinlich auch an all diesen Orten sinnvoll.

  3. Suchen Sie nach Funktionen, die im vorhandenen Code enthalten sind, aber zum neuen Typ gehören.

Vielleicht sehen Sie einen Code, der wie folgt aussieht:

bool SpeedIsAcceptable(int minSpeed, int maxSpeed, int currentSpeed)
{
    return currentSpeed >= minSpeed & currentSpeed < maxSpeed;
}

Sie können die Parameter und nehmen minSpeedund maxSpeedin einen neuen Typ einfügen:

class SpeedRange
{
   public int Min;
   public int Max;
}

bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed)
{
    return currentSpeed >= sr.Min & currentSpeed < sr.Max;
}

Dies ist besser, aber um den neuen Typ wirklich zu nutzen, verschieben Sie die Vergleiche in den neuen Typ:

class SpeedRange
{
   public int Min;
   public int Max;

   bool Contains(int speed)
   {
       return speed >= min & speed < Max;
   }
}

bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed)
{
    return sr.Contains(currentSpeed);
}

Und jetzt kommen wir weiter: Die Implementierung von SpeedIsAcceptable()now sagt, was Sie meinen, und Sie haben eine nützliche, wiederverwendbare Klasse. (Der nächste naheliegende Schritt ist das Einarbeiten SpeedRangein Range<Speed>.)

Wie Sie sehen können, war "Parameterobjekt einführen" ein guter Anfang, aber sein wirklicher Wert war, dass es uns half, einen nützlichen Typ zu entdecken, der in unserem Modell fehlte.


4
Ich würde empfehlen, zuerst "Parameterobjekt einführen" zu versuchen und nur dann auf die anderen Optionen zurückzugreifen, wenn Sie kein gutes Parameterobjekt zum Erstellen finden.
Douglas Leeder

4
ausgezeichnete Antwort. Wenn Sie die Refactoring-Erklärung vor c # syntaktischen Zuckern erwähnt haben, wäre dies meiner Meinung nach höher gewählt worden.
Rpattabi

10
Oh! +1 für "Sie werden wissen, dass Sie es richtig verstanden haben, wenn der Name des neuen Typs offensichtlich ist."
Sean McMillan

20

Wenn es sich um einen Konstruktor handelt, insbesondere wenn mehrere überladene Varianten vorhanden sind, sollten Sie sich das Builder-Muster ansehen:

Foo foo = new Foo()
          .configBar(anything)
          .configBaz(something, somethingElse)
          // and so on

Wenn es sich um eine normale Methode handelt, sollten Sie über die Beziehungen zwischen den übergebenen Werten nachdenken und möglicherweise ein Übertragungsobjekt erstellen.


Hervorragende Antwort. Vielleicht sogar noch relevanter als die Antwort "Parameter in eine Klasse einfügen", die alle (einschließlich mir) gaben.
Wouter Lievens

1
Es ist wahrscheinlich eine schlechte Idee, Ihre Klasse veränderbar zu machen, um zu vermeiden, dass zu viele Parameter an den Konstruktor übergeben werden.
Outlaw Programmer

@outlaw - Wenn die Veränderlichkeit ein Problem darstellt, können Sie die Semantik "Einmal ausführen" problemlos implementieren. Eine große Anzahl von ctor-Parametern weist jedoch häufig auf einen Konfigurationsbedarf hin (oder, wie andere angemerkt haben, auf eine Klasse, die versucht, zu viele Dinge zu tun). (Fortsetzung)
kdgregory

Sie können die Konfiguration zwar externalisieren, dies ist jedoch in vielen Fällen nicht erforderlich, insbesondere wenn sie vom Programmstatus abhängt oder für ein bestimmtes Programm Standard ist (denken Sie an XML-Parser, die Namespace-fähig sein können und mit verschiedenen Tools validiert werden usw.).
kdgregory

Ich mag das Builder-Muster, aber ich trenne meine unveränderlichen und veränderlichen Builder-Typen wie string / StringBuilder, aber ich verwende verschachtelte Klassen: Foo / Foo.Builder. Ich habe ein PowerShell-Skript, um den Code für einfache Datenklassen zu generieren.
Jay Bazuzi

10

Die klassische Antwort darauf besteht darin, eine Klasse zu verwenden, um einige oder alle Parameter zu kapseln. Theoretisch klingt das großartig, aber ich bin der Typ, der Klassen für Konzepte erstellt, die in der Domäne eine Bedeutung haben. Daher ist es nicht immer einfach, diesen Rat anzuwenden.

ZB statt:

driver.connect(host, user, pass)

Du könntest benutzen

config = new Configuration()
config.setHost(host)
config.setUser(user)
config.setPass(pass)
driver.connect(config)

YMMV


5
Ich würde den ersten Code definitiv mehr mögen. Ich stimme zu, dass es eine bestimmte Grenze gibt, oberhalb derer die Anzahl der Parameter hässlich wird, aber für meinen Geschmack wäre 3 akzeptabel.
blabla999

10

Dies wird aus dem Buch von Fowler und Beck zitiert: "Refactoring"

Lange Parameterliste

In unseren frühen Programmiertagen wurde uns beigebracht, alles, was eine Routine benötigt, als Parameter einzugeben. Dies war verständlich, da die Alternative globale Daten waren und globale Daten böse und normalerweise schmerzhaft sind. Objekte ändern diese Situation, denn wenn Sie etwas nicht haben, das Sie benötigen, können Sie jederzeit ein anderes Objekt bitten, es für Sie zu besorgen. Bei Objekten übergeben Sie also nicht alles, was die Methode benötigt. Stattdessen übergeben Sie genug, damit die Methode alles erreicht, was sie benötigt. Vieles, was eine Methode benötigt, ist in der Hostklasse der Methode verfügbar. In objektorientierten Programmen sind Parameterlisten in der Regel viel kleiner als in herkömmlichen Programmen. Dies ist gut, weil lange Parameterlisten schwer zu verstehen sind, weil sie inkonsistent und schwierig zu verwenden sind. und weil Sie sie für immer ändern, wenn Sie mehr Daten benötigen. Die meisten Änderungen werden durch Übergeben von Objekten entfernt, da Sie mit größerer Wahrscheinlichkeit nur ein paar Anfragen stellen müssen, um an ein neues Datenelement zu gelangen. Verwenden Sie Parameter durch Methode ersetzen, wenn Sie die Daten in einem Parameter abrufen können, indem Sie ein Objekt anfordern, das Sie bereits kennen. Dieses Objekt kann ein Feld oder ein anderer Parameter sein. Verwenden Sie "Ganzes Objekt beibehalten", um eine Reihe von Daten aus einem Objekt zu entnehmen und durch das Objekt selbst zu ersetzen. Wenn Sie mehrere Datenelemente ohne logisches Objekt haben, verwenden Sie Parameterobjekt einführen. Es gibt eine wichtige Ausnahme bei diesen Änderungen. In diesem Fall möchten Sie explizit keine Abhängigkeit vom aufgerufenen Objekt zum größeren Objekt erstellen. In diesen Fällen ist es sinnvoll, Daten auszupacken und als Parameter mitzusenden. Achten Sie jedoch auf die damit verbundenen Schmerzen. Wenn die Parameterliste zu lang ist oder sich zu oft ändert, müssen Sie Ihre Abhängigkeitsstruktur überdenken.


7

Ich möchte nicht wie ein weiser Knall klingen, aber Sie sollten auch überprüfen, ob die Daten, die Sie weitergeben, wirklich weitergegeben werden sollten: Das Übergeben von Dingen an einen Konstruktor (oder eine Methode) riecht ein bisschen nach wenig Wert auf das Verhalten eines Objekts.

Verstehen Sie mich nicht falsch: Methoden und Konstrukteure wird manchmal eine Menge Parameter. Versuchen Sie jedoch, Daten mit Verhalten zu kapseln, wenn sie auftreten .

Diese Art von Geruch (da es sich um Refactoring handelt, scheint dieses schreckliche Wort angemessen zu sein ...) kann auch für Objekte erkannt werden, die viele (sprich: beliebige) Eigenschaften oder Getter / Setter haben.


7

Wenn ich lange Parameterlisten sehe, ist meine erste Frage, ob diese Funktion oder dieses Objekt zu viel tut. Erwägen:

EverythingInTheWorld earth=new EverythingInTheWorld(firstCustomerId,
  lastCustomerId,
  orderNumber, productCode, lastFileUpdateDate,
  employeeOfTheMonthWinnerForLastMarch,
  yearMyHometownWasIncorporated, greatGrandmothersBloodType,
  planetName, planetSize, percentWater, ... etc ...);

Natürlich ist dieses Beispiel absichtlich lächerlich, aber ich habe viele echte Programme mit Beispielen gesehen, die nur etwas weniger lächerlich sind, wobei eine Klasse verwendet wird, um viele kaum verwandte oder nicht verwandte Dinge zu speichern, anscheinend nur, weil dasselbe aufrufende Programm beides benötigt oder weil das Der Programmierer dachte zufällig an beide gleichzeitig. Manchmal besteht die einfache Lösung darin, die Klasse in mehrere Teile zu zerlegen, von denen jedes sein eigenes Ding macht.

Nur etwas komplizierter ist es, wenn eine Klasse sich wirklich mit mehreren logischen Dingen befassen muss, wie z. B. einer Kundenbestellung und allgemeinen Informationen über den Kunden. Erstellen Sie in diesen Fällen eine Klasse für den Kunden und eine Klasse für die Bestellung und lassen Sie sie bei Bedarf miteinander sprechen. Also statt:

 Order order=new Order(customerName, customerAddress, customerCity,
   customerState, customerZip,
   orderNumber, orderType, orderDate, deliveryDate);

Wir könnten haben:

Customer customer=new Customer(customerName, customerAddress,
  customerCity, customerState, customerZip);
Order order=new Order(customer, orderNumber, orderType, orderDate, deliveryDate);

Während ich natürlich Funktionen bevorzuge, die nur 1 oder 2 oder 3 Parameter annehmen, müssen wir manchmal akzeptieren, dass diese Funktion realistisch gesehen eine Menge braucht und dass die Anzahl von ihnen nicht wirklich Komplexität erzeugt. Beispielsweise:

Employee employee=new Employee(employeeId, firstName, lastName,
  socialSecurityNumber,
  address, city, state, zip);

Ja, es sind ein paar Felder, aber wahrscheinlich werden wir sie nur in einem Datenbankeintrag speichern oder auf einen Bildschirm oder ähnliches werfen. Hier wird nicht wirklich viel verarbeitet.

Wenn meine Parameterlisten lang werden, bevorzuge ich es sehr, wenn ich den Feldern unterschiedliche Datentypen zuweisen kann. Wie wenn ich eine Funktion sehe wie:

void updateCustomer(String type, String status,
  int lastOrderNumber, int pastDue, int deliveryCode, int birthYear,
  int addressCode,
  boolean newCustomer, boolean taxExempt, boolean creditWatch,
  boolean foo, boolean bar);

Und dann sehe ich es genannt mit:

updateCustomer("A", "M", 42, 3, 1492, 1969, -7, true, false, false, true, false);

Ich mache mir Sorgen. Wenn man sich den Anruf ansieht, ist überhaupt nicht klar, was all diese kryptischen Nummern, Codes und Flags bedeuten. Dies fragt nur nach Fehlern. Ein Programmierer kann leicht über die Reihenfolge der Parameter verwirrt werden und versehentlich zwei wechseln. Wenn sie denselben Datentyp haben, akzeptiert der Compiler dies einfach. Ich hätte viel lieber eine Signatur, in der all diese Dinge Aufzählungen sind, also leitet ein Anruf Dinge wie Type.ACTIVE anstelle von "A" und CreditWatch.NO anstelle von "false" usw. ein.


5

Wenn einige der Konstruktorparameter optional sind, ist es sinnvoll, einen Builder zu verwenden, der die erforderlichen Parameter im Konstruktor abruft, und Methoden für die optionalen Parameter, die den Builder zurückgeben, wie folgt zu verwenden:

return new Shniz.Builder(foo, bar).baz(baz).quux(quux).build();

Die Details hierzu sind in Effective Java, 2nd Ed., P. 11. Für Methodenargumente beschreibt dasselbe Buch (S. 189) drei Ansätze zum Verkürzen von Parameterlisten:

  • Teilen Sie die Methode in mehrere Methoden auf, die weniger Argumente benötigen
  • Erstellen Sie statische Hilfsmitgliedsklassen, um Gruppen von Parametern darzustellen, dh übergeben Sie a DinoDonkeyanstelle von dinounddonkey
  • Wenn Parameter optional sind, kann der obige Builder für Methoden übernommen werden, indem ein Objekt für alle Parameter definiert, die erforderlichen festgelegt und anschließend eine Ausführungsmethode aufgerufen wird

4

Ich würde den Standardkonstruktor und die Eigenschaftssetzer verwenden. C # 3.0 hat eine nette Syntax, um dies automatisch zu tun.

return new Shniz { Foo = foo,
                   Bar = bar,
                   Baz = baz,
                   Quuz = quux,
                   Fred = fred,
                   Wilma = wilma,
                   Barney = barney,
                   Dino = dino,
                   Donkey = donkey
                 };

Die Codeverbesserung besteht darin, den Konstruktor zu vereinfachen und nicht mehrere Methoden unterstützen zu müssen, um verschiedene Kombinationen zu unterstützen. Die "aufrufende" Syntax ist immer noch ein wenig "wortreich", aber nicht wirklich schlimmer als das manuelle Aufrufen der Eigenschaftssetzer.


2
Dies würde ermöglichen, dass ein Objekt t new Shniz () existiert. Eine gute OO-Implementierung würde versuchen, die Möglichkeit zu minimieren, dass Objekte einen unvollständigen Zustand aufweisen.
AnthonyWJones

Im Allgemeinen bietet jede Sprache mit einer nativen Hash- / Wörterbuchsyntax einen angemessenen Ersatz für benannte Parameter (die großartig sind und häufig in diesen Situationen gefordert werden, aber aus irgendeinem Grund ist die einzige populäre Sprache, die sie unterstützt, die schlechteste auf dem Planeten). .
Chaos

4

Sie haben nicht genügend Informationen angegeben, um eine gute Antwort zu gewährleisten. Eine lange Parameterliste ist nicht von Natur aus schlecht.

Shniz (Foo, Bar, Baz, Quux, Fred, Wilma, Barney, Dino, Esel)

könnte interpretiert werden als:

void Shniz(int foo, int bar, int baz, int quux, int fred, 
           int wilma, int barney, int dino, int donkey) { ...

In diesem Fall ist es weitaus besser, eine Klasse zum Einkapseln der Parameter zu erstellen, da Sie den verschiedenen Parametern eine Bedeutung geben, die der Compiler überprüfen und den Code visuell leichter lesen kann. Es erleichtert auch das spätere Lesen und Umgestalten.

// old way
Shniz(1,2,3,2,3,2,1,2);
Shniz(1,2,2,3,3,2,1,2); 

//versus
ShnizParam p = new ShnizParam { Foo = 1, Bar = 2, Baz = 3 };
Shniz(p);

Alternativ, wenn Sie hatten:

void Shniz(Foo foo, Bar bar, Baz baz, Quux quux, Fred fred, 
           Wilma wilma, Barney barney, Dino dino, Donkey donkey) { ...

Dies ist ein ganz anderer Fall, da alle Objekte unterschiedlich sind (und wahrscheinlich nicht durcheinander geraten). Einverstanden, dass es wenig sinnvoll ist, eine Parameterklasse zu erstellen, wenn alle Objekte erforderlich und alle unterschiedlich sind.

Sind einige Parameter zusätzlich optional? Gibt es Methodenüberschreibungen (gleicher Methodenname, aber unterschiedliche Methodensignaturen?)? Diese Art von Details sind alle wichtig für das Beste Antwort.

* Eine Eigenschaftstasche kann ebenfalls nützlich sein, ist jedoch nicht besonders besser, da kein Hintergrund angegeben ist.

Wie Sie sehen können, gibt es mehr als eine richtige Antwort auf diese Frage. Treffen Sie Ihre Wahl.


3

Sie können versuchen, Ihren Parameter in mehrere sinnvolle Strukturen / Klassen zu gruppieren (falls möglich).


2

Ich würde mich im Allgemeinen dem Strukturansatz zuwenden - vermutlich hängen die meisten dieser Parameter in irgendeiner Weise zusammen und repräsentieren den Zustand eines Elements, das für Ihre Methode relevant ist.

Wenn der Parametersatz nicht zu einem aussagekräftigen Objekt gemacht werden kann, ist dies wahrscheinlich ein Zeichen, Shnizdas zu viel bewirkt, und das Refactoring sollte die Aufteilung der Methode in separate Belange beinhalten.


2

Sie können Komplexität gegen Quellcodezeilen eintauschen. Wenn die Methode selbst zu viel tut (Schweizer Messer), versuchen Sie, ihre Aufgaben zu halbieren, indem Sie eine andere Methode erstellen. Wenn die Methode nur einfach ist und zu viele Parameter benötigt, sind die sogenannten Parameterobjekte der richtige Weg.


2

Wenn Ihre Sprache dies unterstützt, verwenden Sie benannte Parameter und machen Sie so viele optionale (mit angemessenen Standardeinstellungen) wie möglich.


1

Ich denke, die von Ihnen beschriebene Methode ist der richtige Weg. Wenn ich eine Methode mit vielen Parametern finde und / oder eine, die in Zukunft wahrscheinlich mehr benötigt, erstelle ich normalerweise ein ShnizParams-Objekt, das durchlaufen werden soll, wie Sie es beschreiben.


1

Wie wäre es, wenn Sie es nicht auf einmal bei den Konstruktoren einstellen, sondern über Eigenschaften / Setter ? Ich habe einige .NET - Klassen gesehen , die diesen Ansatz wie nutzen ProcessKlasse:

        Process p = new Process();

        p.StartInfo.UseShellExecute = false;
        p.StartInfo.CreateNoWindow = true;
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.RedirectStandardError = true;
        p.StartInfo.FileName = "cmd";
        p.StartInfo.Arguments = "/c dir";
        p.Start();

3
C # 3 hat tatsächlich eine Syntax, um dies einfach zu tun: Objektinitialisierer.
Daren Thomas

1

Ich stimme dem Ansatz zu, die Parameter in ein Parameterobjekt (struct) zu verschieben. Überprüfen Sie, ob andere Funktionen ähnliche Parametergruppen verwenden, anstatt sie alle in ein Objekt zu stecken. Ein Parameterobjekt ist wertvoller, wenn es mit mehreren Funktionen verwendet wird, bei denen Sie erwarten, dass sich dieser Parametersatz über diese Funktionen hinweg konsistent ändert. Es kann sein, dass Sie nur einige der Parameter in das neue Parameterobjekt einfügen.


1

Wenn Sie so viele Parameter haben, ist die Wahrscheinlichkeit groß, dass die Methode zu viel leistet. Behandeln Sie dies also zuerst, indem Sie die Methode in mehrere kleinere Methoden aufteilen. Wenn Sie danach immer noch zu viele Parameter haben, versuchen Sie, die Argumente zu gruppieren oder einige der Parameter in Instanzmitglieder umzuwandeln.

Bevorzugen Sie kleine Klassen / Methoden gegenüber großen. Denken Sie an das Prinzip der Einzelverantwortung.


Das Problem mit Instanzmitgliedern und Eigenschaften besteht darin, dass sie 1) beschreibbar sein müssen, 2) möglicherweise nicht festgelegt sind. Im Fall eines Konstruktors gibt es bestimmte Felder, die ich ausfüllen möchte, bevor eine Instanz existieren darf.
rekursiv

@recursive - Ich bin nicht der Meinung, dass Felder / Eigenschaften immer beschreibbar sein müssen. Für kleine Klassen gibt es viele Male, in denen nur Mitglieder Sinn machen.
Brian Rasmussen

1

Benannte Argumente sind eine gute Option (unter der Annahme einer Sprache, die sie unterstützt), um lange (oder sogar kurze!) Parameterlisten zu disambiguieren und gleichzeitig (im Fall von Konstruktoren) zuzulassen, dass die Eigenschaften der Klasse unveränderlich sind, ohne dass eine Existenz erforderlich ist in einem teilweise konstruierten Zustand.

Die andere Option, nach der ich bei dieser Art von Refactor suchen würde, wären Gruppen verwandter Parameter, die möglicherweise besser als unabhängiges Objekt behandelt werden. Am Beispiel der Rectangle-Klasse aus einer früheren Antwort könnte der Konstruktor, der Parameter für x, y, Höhe und Breite verwendet, x und y in ein Point-Objekt zerlegen, sodass Sie drei Parameter an den Konstruktor des Rectangle übergeben können. Oder gehen Sie etwas weiter und machen Sie es zu zwei Parametern (UpperLeftPoint, LowerRightPoint), aber das wäre ein radikaleres Refactoring.


0

Es hängt davon ab, welche Art von Argumenten Sie haben, aber wenn es sich um viele boolesche Werte / Optionen handelt, könnten Sie möglicherweise eine Flag-Aufzählung verwenden?


0

Ich denke, dieses Problem ist eng mit der Domäne des Problems verbunden, das Sie mit der Klasse lösen möchten.

In einigen Fällen kann ein Konstruktor mit 7 Parametern auf eine schlechte Klassenhierarchie hinweisen: In diesem Fall ist die oben vorgeschlagene Hilfsstruktur / -klasse normalerweise ein guter Ansatz, aber dann neigen Sie auch dazu, eine Menge Strukturen zu erhalten, die nur Eigenschaftstaschen sind und nichts Nützliches tun. Der Konstruktor mit 8 Argumenten zeigt möglicherweise auch an, dass Ihre Klasse zu allgemein / zu universell ist, sodass viele Optionen erforderlich sind, um wirklich nützlich zu sein. In diesem Fall können Sie entweder die Klasse umgestalten oder statische Konstruktoren implementieren, die die wirklich komplexen Konstruktoren verbergen: z. Shniz.NewBaz (foo, bar) könnte tatsächlich den realen Konstruktor aufrufen, der die richtigen Parameter übergibt.


0

Eine Überlegung ist, welcher der Werte nach dem Erstellen des Objekts schreibgeschützt ist.

Öffentlich beschreibbare Objekte könnten möglicherweise nach dem Bau vergeben werden.

Woher kommen letztendlich die Werte? Möglicherweise sind einige Werte wirklich extern, während andere tatsächlich aus einer Konfiguration oder globalen Daten stammen, die von der Bibliothek verwaltet werden.

In diesem Fall können Sie den Konstruktor vor externer Verwendung verbergen und eine Erstellungsfunktion dafür bereitstellen. Die Funktion create verwendet die wirklich externen Werte und erstellt das Objekt. Anschließend werden Accessoren verwendet, die nur für die Bibliothek verfügbar sind, um die Erstellung des Objekts abzuschließen.

Es wäre wirklich seltsam, ein Objekt zu haben, das 7 oder mehr Parameter benötigt, um dem Objekt einen vollständigen Zustand zu verleihen, und alle sind wirklich äußerlicher Natur.


0

Wenn eine Klasse einen Konstruktor hat, der zu viele Argumente akzeptiert, ist dies normalerweise ein Zeichen dafür, dass sie zu viele Verantwortlichkeiten hat. Es kann wahrscheinlich in separate Klassen unterteilt werden, die zusammenarbeiten, um dieselben Funktionen bereitzustellen.

Falls Sie wirklich so viele Argumente für einen Konstruktor benötigen, kann Ihnen das Builder-Muster helfen. Das Ziel besteht darin, weiterhin alle Argumente an den Konstruktor zu übergeben, sodass sein Status von Anfang an initialisiert wird und Sie die Klasse bei Bedarf unveränderlich machen können.

Siehe unten :

public class Toto {
    private final String state0;
    private final String state1;
    private final String state2;
    private final String state3;

    public Toto(String arg0, String arg1, String arg2, String arg3) {
        this.state0 = arg0;
        this.state1 = arg1;
        this.state2 = arg2;
        this.state3 = arg3;
    }

    public static class TotoBuilder {
        private String arg0;
        private String arg1;
        private String arg2;
        private String arg3;

        public TotoBuilder addArg0(String arg) {
            this.arg0 = arg;
            return this;
        }
        public TotoBuilder addArg1(String arg) {
            this.arg1 = arg;
            return this;
        }
        public TotoBuilder addArg2(String arg) {
            this.arg2 = arg;
            return this;
        }
        public TotoBuilder addArg3(String arg) {
            this.arg3 = arg;
            return this;
        }

        public Toto newInstance() {
            // maybe add some validation ...
            return new Toto(this.arg0, this.arg1, this.arg2, this.arg3);
        }
    }

    public static void main(String[] args) {
        Toto toto = new TotoBuilder()
            .addArg0("0")
            .addArg1("1")
            .addArg2("2")
            .addArg3("3")
            .newInstance();
    }

}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.