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?
refactoring
rekursiv
quelle
quelle
Antworten:
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.
quelle
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.
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
string
undStringBuilder
. Sie können dies für Ihre eigenen Klassen bekommen. Ich implementiere es gerne als verschachtelte Klasse, daher hat die KlasseC
eine verwandte KlasseC.Builder
. Ich mag auch eine fließende Oberfläche des Builders. Wenn Sie es richtig machen, können Sie die folgende Syntax erhalten:Ich habe ein PowerShell-Skript, mit dem ich den Builder-Code generieren kann, um all dies zu tun, wobei die Eingabe wie folgt aussieht:
So kann ich zur Kompilierungszeit generieren.
partial
Mit 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:
wird
Dieser Ansatz hat jedoch das größte Potenzial, sich positiv auf Ihren Code auszuwirken. Fahren Sie also mit den folgenden Schritten fort:
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.
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.
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:
Sie können die Parameter und nehmen
minSpeed
undmaxSpeed
in einen neuen Typ einfügen:Dies ist besser, aber um den neuen Typ wirklich zu nutzen, verschieben Sie die Vergleiche in den neuen Typ:
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 EinarbeitenSpeedRange
inRange<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.
quelle
Wenn es sich um einen Konstruktor handelt, insbesondere wenn mehrere überladene Varianten vorhanden sind, sollten Sie sich das Builder-Muster ansehen:
Wenn es sich um eine normale Methode handelt, sollten Sie über die Beziehungen zwischen den übergebenen Werten nachdenken und möglicherweise ein Übertragungsobjekt erstellen.
quelle
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:
Du könntest benutzen
YMMV
quelle
Dies wird aus dem Buch von Fowler und Beck zitiert: "Refactoring"
quelle
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.
quelle
Wenn ich lange Parameterlisten sehe, ist meine erste Frage, ob diese Funktion oder dieses Objekt zu viel tut. Erwägen:
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:
Wir könnten haben:
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:
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:
Und dann sehe ich es genannt mit:
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.
quelle
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:
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:
DinoDonkey
anstelle vondino
unddonkey
quelle
Ich würde den Standardkonstruktor und die Eigenschaftssetzer verwenden. C # 3.0 hat eine nette Syntax, um dies automatisch zu tun.
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.
quelle
Sie haben nicht genügend Informationen angegeben, um eine gute Antwort zu gewährleisten. Eine lange Parameterliste ist nicht von Natur aus schlecht.
könnte interpretiert werden als:
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.
Alternativ, wenn Sie hatten:
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.
quelle
Sie können versuchen, Ihren Parameter in mehrere sinnvolle Strukturen / Klassen zu gruppieren (falls möglich).
quelle
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,
Shniz
das zu viel bewirkt, und das Refactoring sollte die Aufteilung der Methode in separate Belange beinhalten.quelle
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.
quelle
Wenn Ihre Sprache dies unterstützt, verwenden Sie benannte Parameter und machen Sie so viele optionale (mit angemessenen Standardeinstellungen) wie möglich.
quelle
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.
quelle
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
Process
Klasse:quelle
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.
quelle
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.
quelle
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.
quelle
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?
quelle
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.
quelle
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.
quelle
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 :
quelle