Ist es in Ordnung, eine Klasse mit nur Eigenschaften für Refactoring-Zwecke zu haben?

79

Ich habe eine Methode, die 30 Parameter akzeptiert. Ich nahm die Parameter und legte sie in eine Klasse, so dass ich nur einen Parameter (die Klasse) an die Methode übergeben konnte. Ist es im Falle eines Refactorings vollkommen in Ordnung, ein Objekt zu übergeben, das alle Parameter kapselt, auch wenn dies alles ist, was es enthält?

Xaisoft
quelle
Die Antworten unten sind gut. Zu diesem Zweck habe ich immer eine Klasse eingerichtet. Verwenden Sie automatische Eigenschaften? msdn.microsoft.com/en-us/library/bb384054.aspx
Harv
Andernfalls als POD-Typ (Plain-Old-Data) bekannt.
new123456
17
Obwohl viele der gegebenen Antworten großartig sind und es in der Tat eine gute Idee ist, diese Parameter in etwas umzufassen, das etwas einfacher zu handhaben ist, würde ich sagen, dass die Tatsache, dass diese Methode 30 verschiedene Daten benötigt, um ihre Sache zu erledigen, ein Zeichen ist dass es zu viel tut. Andererseits haben wir die fragliche Methode nicht gesehen oder wissen gar nicht, wozu sie dient.
Benutzer
1
Haben Sie diese Methode, die alle Parameter übernommen hat, in die neue Klasse verschoben? Oder nimmt die alte Methode nur die neue Klasse als einzigen Parameter? Ich würde versuchen, die Methode in die neue Klasse aufzunehmen, wenn es Sinn macht. Klassen mit nur Daten und ohne Methoden werden als anämische Klassen bezeichnet, und es fühlt sich etwas weniger OO an. Keine feste Regel, nur etwas zum Anschauen.
Kurt
@ Michael: Ich stimme dir zu. Ein weiterer Punkt - Ich glaube, Sie sollten kein methodenspezifisches Objekt erstellen, es sei denn, sein internes Objekt hat eine Beziehung außerhalb der Methode.
Saturn Technologies

Antworten:

75

Das ist eine großartige Idee. In der Regel werden Datenverträge beispielsweise in WCF ausgeführt.

Ein Vorteil dieses Modells besteht darin, dass der Verbraucher der Klasse beim Hinzufügen eines neuen Parameters nicht nur Änderungen vornehmen muss, um den Parameter hinzuzufügen.

Wie David Heffernan erwähnt, kann es helfen, den Code selbst zu dokumentieren:

FrobRequest frobRequest = new FrobRequest
{
    FrobTarget = "Joe",
    Url = new Uri("http://example.com"),
    Count = 42,
};
FrobResult frobResult = Frob(frobRequest);
McKay
quelle
Toll. Ich hatte den Eindruck, dass es nutzlos ist, nur eine Klasse mit Eigenschaften und ohne Methoden zu haben.
Xaisoft
2
Wenn es nur Datenelemente enthält, ziehe ich es vor, es zu strukturieren. Die Verwendung von struct erweckt den Eindruck, dass es sich nur um Daten handelt.
Yousf
28
Die Verwendung von struct hat Nebenwirkungen wie das Kopieren beim Übergeben der Semantik. Bitte stellen Sie sicher, dass Sie sich dessen bewusst sind, bevor Sie eine solche Entscheidung treffen
Adrian Zanescu
So werden normalerweise Javascript-Module geschrieben, und es scheint die beste Methode zu sein, um mehr oder weniger Parameter zu akzeptieren, wenn das Modul ausgereift ist. someFunction ({myParam1: 'etwas', myParam2: 'etwasElse'});
Kristian
63

Während andere Antworten hier richtig darauf hinweisen, dass das Übergeben einer Instanz einer Klasse besser ist als das Übergeben von 30 Parametern, sollten Sie sich bewusst sein, dass eine große Anzahl von Parametern ein Symptom für ein zugrunde liegendes Problem sein kann.

Beispielsweise nehmen statische Methoden häufig an Anzahl von Parametern zu, da sie die ganze Zeit über Instanzmethoden gewesen sein sollten, und Sie übergeben viele Informationen, die in einer Instanz dieser Klasse einfacher verwaltet werden könnten.

Alternativ können Sie nach Möglichkeiten suchen, die Parameter in Objekte einer höheren Abstraktionsebene zu gruppieren. Das Ablegen einer Reihe nicht verwandter Parameter in eine einzelne Klasse ist ein letzter Ausweg, IMO.

Siehe Wie viele Parameter sind zu viele? für einige weitere Ideen dazu.

RedFilter
quelle
Inwiefern hängen diese Parameter nicht zusammen? Sie werden alle von dieser Methode verwendet. Das ist eine sehr starke Beziehung. Darüber hinaus ist es im Allgemeinen vorzuziehen, Parameter auf dem Stapel anzugeben. Denken Sie an Multithreading.
David Heffernan
12
Das kann nicht beantwortet werden, ohne den OP-Code zu sehen, aber ich bin nicht der Meinung, dass zwei Werte, die in einer Methode zusammen verwendet werden, eine starke Beziehung haben. Wenn dies wahr wäre, würde OO-Design bedeuten, dass eine große Klasse erstellt wird, die alle möglichen Eigenschaften enthält, die in Ihrer Anwendung verwendet werden.
RedFilter
Nein, Sie haben Recht, nicht unbedingt verwandt. Aber auch nicht unbedingt unabhängig. Also, wie Sie sagen, müsste Code sehen, um sicher zu sein.
David Heffernan
23
30 Parameter? Ich würde mich mit höchstwahrscheinlich einer nicht verwandten Methode zufrieden geben, die wahrscheinlich auf eine zu lange Methode hinweist, eine hohe zyklomatische Komplexität aufweist und ein Paradies für Fehler ist. Bedenken Sie, dass es sich um eine Methode handelt, deren Verhalten mindestens 30 Dimensionen aufweist, in denen es variieren kann. Wenn es Unit-Tests für diese Methode gibt, möchte ich nicht derjenige sein, der sie TBH schreibt.
Steve Rowbotham
3
Ein weiteres wahrscheinliches Szenario ist, dass alle Parameter zusammenhängen, aber schlecht organisiert sind. Es ist sehr wahrscheinlich, dass Gruppen von Parametern zu unterschiedlichen Objekten gebündelt und nicht stückweise weitergegeben werden sollten. Angenommen, ich habe eine Methode, die Informationen wie "Person", "Adresse", "Rezept" verarbeitet. Diese können leicht 30 Parameter betragen, wenn jede der einzelnen Informationen separat in meine Methode übernommen wird.
Nate CK
25

Es ist ein guter Anfang. Aber jetzt, wo Sie diese neue Klasse haben, sollten Sie Ihren Code auf den Kopf stellen. Verschieben Sie die Methode, die diese Klasse als Parameter verwendet, in Ihre neue Klasse (übergeben Sie natürlich eine Instanz der ursprünglichen Klasse als Parameter). Jetzt haben Sie eine große Methode, allein in einer Klasse, und es wird einfacher sein, sie in kleinere, besser handhabbare und testbare Methoden zu zerlegen. Einige dieser Methoden werden möglicherweise wieder in die ursprüngliche Klasse verschoben, aber ein angemessener Teil wird wahrscheinlich in Ihrer neuen Klasse verbleiben. Sie sind über " Parameterobjekt einführen" hinausgegangen, um die Methode durch das Methodenobjekt zu ersetzen .

Eine Methode mit dreißig Parametern ist ein ziemlich starkes Zeichen dafür, dass die Methode zu lang und zu kompliziert ist. Zu schwer zu debuggen, zu schwer zu testen. Sie sollten also etwas dagegen tun, und "Parameterobjekt einführen" ist ein guter Ausgangspunkt.

Carl Manaster
quelle
4
Das ist ABSOLUT WICHTIG! Das Erstellen dieser Attributpakete ist nur deshalb großartig, weil es der erste Schritt zum Erstellen neuer Klassen mit eigenen Methoden ist und Sie so ziemlich immer Methoden finden, die zu Ihren neuen Klassen gehören - suchen Sie nach ihnen! Ich denke, dies ist die kritischste Antwort in dieser Gruppe, sie sollte ganz oben sein.
Bill K
15

Obwohl das Refactoring eines Parameterobjekts an sich keine schlechte Idee ist, sollte es nicht verwendet werden, um das Problem zu verbergen, dass eine Klasse, die 30 von einer anderen Stelle bereitgestellte Daten benötigt, immer noch nach Code riecht. Das Refactoring von Introduct Parameter Object sollte wahrscheinlich als ein Schritt auf dem Weg zu einem umfassenderen Refactoring-Prozess angesehen werden und nicht als das Ende dieses Verfahrens.

Eines der Probleme, auf das es nicht wirklich eingeht, ist das von Feature Envy. Bedeutet die Tatsache, dass die Klasse, an die das Parameterobjekt übergeben wird, so sehr an den Daten einer anderen Klasse interessiert ist, nicht, dass die Methoden, die mit diesen Daten arbeiten, möglicherweise an den Ort verschoben werden sollten, an dem sich die Daten befinden? Es ist wirklich besser, Cluster von Methoden und Daten zu identifizieren, die zusammengehören, und sie in Klassen zu gruppieren, wodurch die Kapselung erhöht und Ihr Code flexibler wird.

Nach mehreren Iterationen der Aufteilung des Verhaltens und der Daten, mit denen es arbeitet, in separate Einheiten sollten Sie feststellen, dass Sie keine Klassen mit einer enormen Anzahl von Abhängigkeiten mehr haben. Dies ist immer ein besseres Endergebnis, da Ihr Code dadurch geschmeidiger wird.

Steve Rowbotham
quelle
10

Das ist eine ausgezeichnete Idee und eine sehr verbreitete Lösung für das Problem. Methoden mit mehr als 2 oder 3 Parametern werden exponentiell immer schwieriger zu verstehen.

Wenn Sie all dies in einer einzigen Klasse zusammenfassen, wird der Code viel klarer. Da Ihre Eigenschaften Namen haben, können Sie selbstdokumentierenden Code wie folgt schreiben:

params.height = 42;
params.width = 666;
obj.doSomething(params);

Wenn Sie viele Parameter haben, ist die Alternative, die auf der Positionsidentifikation basiert, natürlich einfach schrecklich.

Ein weiterer Vorteil besteht darin, dass dem Schnittstellenvertrag zusätzliche Parameter hinzugefügt werden können, ohne dass an allen Anrufstellen Änderungen erzwungen werden müssen. Dies ist jedoch nicht immer so trivial, wie es scheint. Wenn unterschiedliche Aufrufstellen unterschiedliche Werte für den neuen Parameter erfordern, ist es schwieriger, sie zu ermitteln als mit dem parameterbasierten Ansatz. Beim parameterbasierten Ansatz erzwingt das Hinzufügen eines neuen Parameters an jeder Aufrufstelle eine Änderung, um den neuen Parameter bereitzustellen, und Sie können den Compiler die Aufgabe übernehmen, alle zu finden.

David Heffernan
quelle
9

Martin Fowler nennt dieses Introduce Parameter Object in seinem Buch Refactoring . Mit diesem Zitat würden es nur wenige eine schlechte Idee nennen.

Austin Salonen
quelle
1
Ja, aber für 30 Parameter stimmt an anderer Stelle etwas nicht, was zuerst behoben werden muss
manojlds
5

30 Parameter ist ein Chaos. Ich finde es viel schöner, eine Klasse mit den Eigenschaften zu haben. Sie können sogar mehrere "Parameterklassen" für Gruppen von Parametern erstellen, die in dieselbe Kategorie passen.

C.Evenhuis
quelle
3

Sie können auch eine Struktur anstelle einer Klasse verwenden.

Aber was Sie versuchen zu tun, ist sehr verbreitet und eine großartige Idee!

Tad Donaghe
quelle
Eigentlich habe ich über die Verwendung einer Struktur nachgedacht, aber ich war gespannt, ob es Nachteile gibt, wenn ich eine Struktur verwende.
Xaisoft
Warum David? Was hat die Anzahl der Felder damit zu tun? Nur neugierig.
Tad Donaghe
2
Aus Leistungsgründen. Während es semantisch sinnvoller ist, hier eine Struktur zu verwenden, da nur der Wert jedes Felds von Bedeutung ist und die Referenzidentität irrelevant ist, sind etwa 30 Felder mehr zu kopieren (sagen wir, es handelt sich meistens um Referenztypen: 120 Byte bei 32 Bit und 240 Byte bei 64) als mit einer Klasse (4 oder 8 Bytes). Die Copy-by-Value-Natur von Strukturen bedeutet jedoch, dass das Kopieren über den Zugriff schneller ist als bei einem Referenztyp. Daher ist der Schwellenwert, bei dem die Struktur weniger effizient ist, höher als die oben angegebene 1-Zeiger-Größe. Bei> 4 Zeigergrößen ist es Zeit, dies zu berücksichtigen.
Jon Hanna
Genial. Danke für die Erklärung! :)
Tad Donaghe
3

Es kann sinnvoll sein, eine Plain Old Data- Klasse zu verwenden, unabhängig davon, ob Sie ein Refactoring durchführen oder nicht. Ich bin gespannt, warum Sie das nicht gedacht haben.

Jon Hanna
quelle
Ich erinnere mich nicht genau, aber ich denke, als ich vor einiger Zeit hier irgendwo erwähnte, dass ich eine Klasse mit nur Eigenschaften erstellt habe, wurde damals darauf herabgesehen. Zu Ihrem zweiten Punkt sagen Sie, dass nur Parameter, die sich nicht ändern, in der Methode übergeben werden sollen. Mit anderen Worten, wenn die Methode den Parameter ändert, sollte er nicht als Parameter übergeben werden.
Xaisoft
Klassen, die nur Eigenschaften sind, können einen schlechten Geruch haben (siehe Steve Rowbothams Antwort), der auf etwas hinweist, das stattdessen eine "vollständige" Klasse sein sollte. Ein gutes Zeichen ist, wenn Sie ähnliche Klassen mehr als einmal verwenden. Dies ist jedoch nicht immer der Fall, und bei einem Wechsel zwischen einer 30-Feld-Klasse und einer 30-Parameter-Methode gibt es einen guten Aufruf, sich an die erstere zu lehnen. Außerdem kann es ein Anfang sein, diese "volle" Klasse aufzubauen.
Jon Hanna
... Ich entferne meinen Kommentar zur Unveränderlichkeit, da ich mehr daran dachte, dass dies nur eine öffentliche Methode ist (wobei ein "Anfrage" -Objekt die Absicht des Aufrufers beschreibt). Hier kann das Ändern der "Anfrage" zu Verwirrung führen. In einem einmaligen Fall ist es einfacher, veränderlich zu sein und zu bauen, während Sie gehen. Unveränderlichkeit würde nur bedeuten, einen Aufruf mit 30 Argumenten durch einen Konstruktor mit 30 Argumenten zu ersetzen, gefolgt von einem Aufruf, sodass es keinen Gewinn geben würde.
Jon Hanna
3

Vielleicht sind die optionalen und benannten Parameter von C # 4.0 eine gute Alternative dazu?

Auf jeden Fall kann die von Ihnen beschriebene Methode auch zur Abstraktion des Programmverhaltens geeignet sein. Zum Beispiel könnten Sie eine Standardfunktion SaveImage(ImageSaveParameters saveParams)in einer Schnittstelle haben, die ImageSaveParametersauch eine Schnittstelle ist, und können abhängig vom Bildformat zusätzliche Parameter haben. Zum Beispiel JpegSaveParametershat eine Quality-Eigenschaft, während PngSaveParameterseine BitDepth-Eigenschaft hat.

So funktioniert das Speichern-Speichern-Dialogfeld in Paint.NET, so dass es ein sehr reales Beispiel ist.

loog
quelle
3

Wie bereits erwähnt: Dies ist der richtige Schritt, aber beachten Sie auch Folgendes:

  • Ihre Methode ist möglicherweise zu komplex (Sie sollten in Betracht ziehen, sie in mehrere Methoden zu unterteilen oder sie sogar in eine separate Klasse umzuwandeln).
  • Wenn Sie die Klasse für die Parameter erstellen, machen Sie sie unveränderlich
  • Wenn viele der Parameter null sein oder einen Standardwert haben könnten, möchten Sie möglicherweise das Builder-Muster für Ihre Klasse verwenden.
Erhalten
quelle
0

So viele tolle Antworten hier. Ich möchte meine zwei Cent hinzufügen.

Parameterobjekt ist ein guter Anfang. Aber es könnte noch mehr getan werden. Betrachten Sie Folgendes (Rubinbeispiele):

/ 1 / Anstatt einfach alle Parameter zu gruppieren, prüfen Sie, ob eine sinnvolle Gruppierung von Parametern möglich ist. Möglicherweise benötigen Sie mehr als ein Parameterobjekt.

def display_line(startPoint, endPoint, option1, option2)

könnte werden

def display_line(line, display_options)

/ 2 / Das Parameterobjekt hat möglicherweise eine geringere Anzahl von Eigenschaften als die ursprüngliche Anzahl von Parametern.

def double_click?(cursor_location1, control1, cursor_location2, control2)

könnte werden

def double_click?(first_click_info, second_click_info) 
                       # MouseClickInfo being the parameter object type 
                       # having cursor_location and control_at_click as properties

Mithilfe solcher Verwendungen können Sie Möglichkeiten entdecken, diesen Parameterobjekten ein aussagekräftiges Verhalten hinzuzufügen. Sie werden feststellen, dass sie ihren anfänglichen Datenklassengeruch früher zu Ihrem Komfort abschütteln . : -)

rpattabi
quelle