Vermeiden Sie Konstruktoren mit vielen Argumenten

10

Ich habe also eine Fabrik, die Objekte verschiedener Klassen erstellt. Die möglichen Klassen stammen alle von einem abstrakten Vorfahren. Die Factory verfügt über eine Konfigurationsdatei (JSON-Syntax) und entscheidet abhängig von der Konfiguration des Benutzers, welche Klasse erstellt werden soll.

Um dies zu erreichen, verwendet die Factory boost :: property_tree für das JSON-Parsing. Er geht durch den Baum und entscheidet, welches konkrete Objekt er erstellt.

Die Produktobjekte haben jedoch viele Felder (Attribute). Abhängig von der konkreten Klasse hat das Objekt ungefähr 5-10 Attribute, in Zukunft vielleicht sogar mehr.

Ich bin mir also nicht sicher, wie der Konstruktor der Objekte aussehen soll. Ich kann mir zwei Lösungen vorstellen:

1) Der Konstruktor des Produkts erwartet jedes Attribut als Parameter, sodass der Konstruktor mehr als 10 Parameter erhält. Dies ist hässlich und führt zu langen, unlesbaren Codezeilen. Der Vorteil ist jedoch, dass die Factory den JSON analysieren und den Konstruktor mit den richtigen Parametern aufrufen kann. Die Produktklasse muss nicht wissen, dass sie aufgrund der JSON-Konfiguration erstellt wurde. Es muss nicht wissen, dass es sich überhaupt um JSON oder Konfiguration handelt.

2) Der Konstruktor des Produkts erwartet nur ein Argument, das property_tree-Objekt. Dann kann es die benötigten Informationen analysieren. Wenn Informationen in der Konfiguration fehlen oder außerhalb der Grenzen liegen, kann jede Produktklasse ordnungsgemäß reagieren. Die Fabrik muss nicht wissen, welche Argumente von den verschiedenen Produkten benötigt werden. Das Werk muss auch nicht wissen, wie es bei falscher Konfiguration reagieren soll. Und die Konstruktorschnittstelle ist einheitlich und klein. Als Nachteil muss das Produkt jedoch die erforderlichen Informationen aus dem JSON extrahieren, damit es weiß, wie es aufgebaut ist.

Ich bevorzuge eher Lösung 2). Ich bin mir jedoch nicht sicher, ob dies ein gutes Fabrikmuster ist. Es fühlt sich irgendwie falsch an, das Produkt wissen zu lassen, dass es mit der JSON-Konfiguration erstellt wurde. Auf der anderen Seite können neue Produkte sehr einfach eingeführt werden.

Irgendwelche Meinungen dazu?

lugge86
quelle
1
Ich bin deinem Link gefolgt. Es gibt ein Beispiel in der bestbewerteten Antwort von Ratschenfreak. Aber welches Problem löst dieser "Builder" -Ansatz? Es gibt die Codezeile DataClass data = builder.createResult ();. Die Methode createResults () - muss jedoch noch die 10 Parameter in das DataClass-Objekt übertragen. Aber wie? Es scheint, dass Sie nur noch eine Abstraktionsebene haben, aber der Konstruktor der DataClass wird nicht kleiner.
lugge86
Schauen Sie sich Builder und Prototyp an.
Silviu Burcea
Silviu Burcea habe ich. Wie erhält der Builder bei Verwendung von Builder die Parameter in das Produkt? Irgendwo muss es eine fette Schnittstelle geben. Der Builder ist nur noch eine Schicht, aber irgendwie müssen die Parameter ihren Weg in die Produktklasse finden.
lugge86
1
Wenn Ihre Klasse zu groß ist, wird das Verschieben um Konstruktorargumente nicht zu groß .
Telastyn

Antworten:

10

Ich würde Option 2 nicht ausführen, da Sie dann die Konstruktion Ihres Objekts für immer mit dem Parsen von Boost-Eigenschaftsbäumen verknüpft haben. Wenn Sie mit einer Klasse vertraut sind, die so viele Parameter benötigt, sollten Sie mit einem Konstruktor vertraut sein, der so viele Parameter benötigt, so ist das Leben!

Wenn Ihr Hauptanliegen die Lesbarkeit von Code ist, können Sie das Builder-Muster verwenden. Dies ist im Grunde die C ++ / Java-Notlösung, da keine benannten Argumente vorhanden sind. Am Ende haben Sie Dinge, die so aussehen:

MyObject o = MyObject::Builder()
               .setParam1(val1)
               .setParam2(val2)
               .setParam3(val3)
             .build();

Jetzt hat MyObject einen privaten Konstruktor, der in Builder :: build aufgerufen wird. Das Schöne ist, dass dies der einzige Ort ist, an dem Sie jemals einen Konstruktor mit 10 Parametern aufrufen müssen. Die Baumfactory der Boost-Eigenschaft verwendet den Builder. Wenn Sie anschließend ein MyObject direkt oder aus einer anderen Quelle erstellen möchten, durchlaufen Sie den Builder. Mit dem Builder können Sie jeden Parameter bei der Übergabe eindeutig benennen, damit er besser lesbar ist. Dies fügt natürlich etwas Boilerplate hinzu, sodass Sie entscheiden müssen, ob es sich lohnt, wenn Sie nur den chaotischen Konstruktor aufrufen oder einige Ihrer vorhandenen Parameter in Strukturen usw. zusammenfassen. Wirf einfach eine andere Option auf den Tisch.

https://en.wikipedia.org/wiki/Builder_pattern#C.2B.2B_Example

Nir Friedman
quelle
5

Verwenden Sie NICHT den zweiten Ansatz.

Dies ist definitiv nicht die Lösung und würde nur dazu führen, dass Klassen in Ihrer Geschäftslogik instanziiert werden, anstatt in dem Teil Ihrer App, in dem sich die Fabriken befinden.

Entweder:

  • Versuchen Sie, bestimmte Parameter, die ähnliche Dinge zu repräsentieren scheinen, in Objekte zu gruppieren
  • Teilen Sie die aktuelle Klasse in mehrere kleinere Klassen auf (eine Serviceklasse mit 10 Parametern scheint zu viele Dinge zu tun)
  • Lassen Sie es so, wie es ist, wenn Ihre Klasse eigentlich kein Dienst, sondern ein Wertobjekt ist

Sofern es sich bei dem von Ihnen erstellten Objekt nicht um eine Klasse handelt, die für das Speichern von Daten verantwortlich ist, sollten Sie versuchen, den Code umzugestalten und die große Klasse in kleinere aufzuteilen.

Andy
quelle
Nun, die bsiness-Logik verwendet eine Factory, die die Produkte zurückgibt, daher sieht die Geschäftslogik das JSON / ptree-Zeug nicht. Aber ich verstehe Ihren Standpunkt, Parser-Code im Konstruktor zu haben, fühlt sich falsch an.
lugge86
Die Klasse repräsentiert ein Widget in einer GUI für ein eingebettetes System, daher scheinen 5+ Attribute für mich in Ordnung zu sein: x_coord, y_coord, Hintergrundfarbe, Rahmengröße, Rahmenfarbe, Text ...
lugge86
1
@ lugge86 Obwohl Sie eine Factory zum Parsen des JSON verwenden und somit das Aufrufen newoder Erstellen von Objekten in Ihrer Geschäftslogik vermeiden , ist das Design nicht sehr gut. Schauen Sie sich die Suche nicht Dinge von Miško Hevery sprechen , die in mehr Tiefe erklärt , warum Sie die Fabrik Ansatz angedeutet sowohl schlecht aus Tests und Lese Sicht. Außerdem scheint Ihre Klasse ein Datenobjekt zu sein, und für diese ist es im Allgemeinen in Ordnung, mehr Parameter als die reguläre Serviceklasse zu haben. Ich würde mich nicht allzu sehr darum kümmern.
Andy
Ich fühle mich gut mit meinem Fabrikansatz, aber ich werde Ihrem Link folgen und darüber nachdenken. Die Fabrik ist in diesem Thema jedoch nicht in Frage. Die Frage ist immer noch, wie man die Produkte einrichtet ...
lugge86
"Eine Serviceklasse mit 10 Parametern zu haben, scheint, als würde die Klasse zu viele Dinge tun" Nicht beim maschinellen Lernen. Jeder ML-Algorithmus hätte Tonnen von einstellbaren Parametern. Ich frage mich, wie ich beim Codieren von ML richtig damit umgehen soll.
Siyuan Ren
0

Option 2 ist fast richtig.

Eine verbesserte Option 2

Erstellen Sie eine "nach vorne gerichtete" Klasse, deren Aufgabe es ist, dieses JSON-Strukturobjekt zu nehmen, die Bits auszuwählen und die Factory-Konstruktoren aufzurufen. Es nimmt, was die Fabrik macht und gibt es dem Kunden.

  • Die Fabrik hat absolut keine Ahnung, dass es so ein JSON-Ding überhaupt gibt.
  • Der Client muss nicht wissen, welche spezifischen Bits die Fabrik benötigt.

Grundsätzlich sagt das "Frontend" zu den 2 Bobs: "Ich kümmere mich um die redigierten Kunden, damit die Ingenieure nicht müssen! Ich habe menschliche Fähigkeiten!" Armer Tom. Wenn er nur gesagt hätte "Ich entkopple den Kunden vom Bau. Dieses Ergebnis ist eine sehr zusammenhängende Fabrik"; er könnte seinen Job behalten haben.

Zu viele Argumente?

Nicht für die Client-Front-End-Kommunikation.

Frontend - Fabrik? Wenn nicht 10 Parameter, dann ist das Beste, was Sie tun können, das Auspacken zu verschieben, wenn nicht das ursprüngliche JSON-Ding, dann etwas DTO. Ist das besser, als den JSON an die Fabrik weiterzugeben? Gleicher Unterschied, sage ich.

Ich würde dringend in Betracht ziehen, einzelne Parameter zu übergeben. Halten Sie sich an das Ziel einer sauberen, zusammenhängenden Fabrik. Vermeiden Sie die Bedenken der @ DavidPacker-Antwort.

Milderung "zu vieler Argumente"

  • Fabrik- oder Klassenkonstrukteure

    • Es werden nur Argumente für eine bestimmte Klassen- / Objektkonstruktion verwendet.
    • Standardparameter
    • optionale Parameter
    • benannte Argumente
  • Gruppierung von Front-End-Argumenten

    • Untersucht, bewertet, validiert, setzt usw. Argumentwerte, die von den obigen Konstruktorsignaturen geleitet werden.
Radarbob
quelle
"Die Fabrik hat absolut keine Ahnung, dass es so ein JSON-Ding überhaupt gibt" - nun, wofür ist die Fabrik dann? Es verbirgt die Details der Produkterstellung sowohl vor dem Produkt als auch vor dem Verbraucher. Warum sollte noch eine Klasse helfen? Mir geht es gut mit der Fabrik, die JSON spricht. Man kann eine andere Factory zum Parsen von XML implementieren und in Zukunft "Abstract Factory" implementieren ...
lugge86
Mr. Factory: "Welches Objekt möchten Sie? ... Was ist das? Sagen Sie mir einfach, welches Klassenobjekt gebaut werden soll." Die Konfigurationsdatei JSON ist eine Datenquelle, da Onkel Bob sagt, "es ist ein Implementierungsdetail." Es könnte aus einer anderen Quelle und / oder in einer anderen Form stammen. Im Allgemeinen möchten wir uns von den spezifischen Datenquellendetails entkoppeln. Sollte sich die Quelle oder das Formular ändern, wird das Werk dies nicht tun. Bei einer Quelle + Parser und einer Factory als entkoppelte Module sind beide wiederverwendbar.
Radarbob