Ich habe einige große Objekte (mehr als 3 Felder), die unveränderlich sein können und sollten. Jedes Mal, wenn ich auf diesen Fall stoße, neige ich dazu, Konstruktor-Abscheulichkeiten mit langen Parameterlisten zu erstellen.
Es fühlt sich nicht richtig an, ist schwer zu bedienen und die Lesbarkeit leidet.
Es ist noch schlimmer, wenn die Felder eine Art Sammlungstyp wie Listen sind. Ein einfaches addSibling(S s)
würde die Objekterstellung so sehr erleichtern, macht das Objekt jedoch veränderlich.
Was benutzt ihr in solchen Fällen?
Ich bin auf Scala und Java, aber ich denke, das Problem ist sprachunabhängig, solange die Sprache objektorientiert ist.
Lösungen, die mir einfallen:
- "Konstruktor-Greuel mit langen Parameterlisten"
- Das Builder-Muster
java
oop
scala
immutability
Malax
quelle
quelle
Antworten:
Nun, Sie möchten sowohl ein leichter lesbares als auch ein unveränderliches Objekt, sobald es erstellt wurde?
Ich denke, eine fließende Schnittstelle, die richtig gemacht wurde, würde Ihnen helfen.
Es würde so aussehen (rein erfundenes Beispiel):
Ich habe "RICHTIG GEMACHT" in Fettdruck geschrieben, weil die meisten Java-Programmierer fließende Schnittstellen falsch verstehen und ihr Objekt mit der zum Erstellen des Objekts erforderlichen Methode verschmutzen, was natürlich völlig falsch ist.
Der Trick ist, dass nur die build () -Methode tatsächlich ein Foo erstellt (daher kann Ihr Foo unveränderlich sein).
FooFactory.create () , wobeiXXX (..) und withXXX (..) alle "etwas anderes" erstellen.
Dass etwas anderes eine FooFactory sein kann, hier ist eine Möglichkeit, es zu tun ...
Ihre FooFactory würde so aussehen:
quelle
Foo
Objekt, anstatt eine separate aufweistFooFactory
.FooImpl
Konstruktor mit 8 Parametern. Was ist die Verbesserung?immutable
, und ich hätte Angst davor, dass Leute Fabrikobjekte wiederverwenden, weil sie denken, dass dies der Fall ist. Ich meine:FooFactory people = FooFactory.create().withType("person"); Foo women = people.withGender("female").build(); Foo talls = people.tallerThan("180m").build();
wotalls
würde jetzt nur noch Frauen sein. Dies sollte mit einer unveränderlichen API nicht passieren.In Scala 2.8 können Sie benannte und Standardparameter sowie die
copy
Methode für eine Fallklasse verwenden. Hier ist ein Beispielcode:quelle
Betrachten Sie dies in Scala 2.8:
Dies hat natürlich einige Probleme. Versuchen Sie zum Beispiel,
espouse
undOption[Person]
und dann zwei Personen miteinander zu heiraten. Ich kann mir keinen Weg vorstellen, das zu lösen, ohne auf einenprivate var
und / oder einenprivate
Konstrukteur und eine Fabrik zurückzugreifen.quelle
Hier sind einige weitere Optionen:
Option 1
Machen Sie die Implementierung selbst veränderbar, trennen Sie jedoch die Schnittstellen, die sie veränderbar und unveränderlich macht. Dies ist dem Design der Swing-Bibliothek entnommen.
Option 2
Wenn Ihre Anwendung eine große, aber vordefinierte Menge unveränderlicher Objekte enthält (z. B. Konfigurationsobjekte), können Sie das Spring- Framework verwenden.
quelle
Es hilft, sich daran zu erinnern, dass es verschiedene Arten von Unveränderlichkeit gibt . Für Ihren Fall denke ich, dass die Unveränderlichkeit von "Eis am Stiel" wirklich gut funktionieren wird:
Sie initialisieren also Ihr Objekt und setzen dann ein "Freeze" -Flag, das angibt, dass es nicht mehr beschreibbar ist. Am besten verstecken Sie die Mutation hinter einer Funktion, damit die Funktion für Clients, die Ihre API verwenden, immer noch rein ist.
quelle
clone()
, um neue Instanzen abzuleiten.freeze()
Methode aufrufen soll, aber nicht, können die Dinge ebenfalls hässlich werden.Sie können die unveränderlichen Objekte auch Methoden verfügbar machen, die wie Mutatoren aussehen (wie addSibling), sie jedoch eine neue Instanz zurückgeben lassen. Das machen die unveränderlichen Scala-Sammlungen.
Der Nachteil ist, dass Sie möglicherweise mehr Instanzen als erforderlich erstellen. Dies gilt auch nur, wenn zwischenzeitlich gültige Konfigurationen vorhanden sind (wie bei einigen Knoten ohne Geschwister, was in den meisten Fällen in Ordnung ist), es sei denn, Sie möchten sich nicht mit teilweise erstellten Objekten befassen.
Beispielsweise ist eine Diagrammkante, die noch kein Ziel hat, keine gültige Diagrammkante.
quelle
Betrachten Sie vier Möglichkeiten:
Für mich ist jeder von 2, 3 und 4 an eine unterschiedliche Situation angepasst. Der erste ist aus den vom OP angeführten Gründen schwer zu lieben und ist im Allgemeinen ein Symptom für ein Design, das ein gewisses Kriechen erlitten hat und etwas umgestaltet werden muss.
Was ich als (2) aufführe, ist gut, wenn es keinen Zustand hinter der 'Fabrik' gibt, während (3) das Design der Wahl ist, wenn es einen Zustand gibt. Ich verwende (2) anstelle von (3), wenn ich mich nicht um Threads und Synchronisation kümmern möchte, und ich muss mich nicht darum kümmern, ein teures Setup über die Produktion vieler Objekte zu amortisieren. (3) wird dagegen aufgerufen, wenn echte Arbeit in den Bau der Fabrik fließt (Einrichten von einem SPI, Lesen von Konfigurationsdateien usw.).
Schließlich erwähnte die Antwort eines anderen die Option (4), bei der Sie viele kleine unveränderliche Objekte haben und das bevorzugte Muster darin besteht, Nachrichten von alten zu erhalten.
Beachten Sie, dass ich kein Mitglied des 'Pattern Fan Clubs' bin - sicher, einige Dinge sind es wert, nachgeahmt zu werden, aber es scheint mir, dass sie ein nicht hilfreiches Eigenleben führen, wenn die Leute ihnen Namen und lustige Hüte geben.
quelle
Eine weitere mögliche Option ist die Umgestaltung, um weniger konfigurierbare Felder zu haben. Wenn Gruppen von Feldern (meistens) nur miteinander arbeiten, sammeln Sie sie zu einem eigenen kleinen unveränderlichen Objekt. Die Konstruktoren / Builder dieses "kleinen" Objekts sollten besser zu verwalten sein, ebenso wie der Konstruktor / Builder für dieses "große" Objekt.
quelle
Ich benutze C # und das sind meine Ansätze. Erwägen:
Option 1. Konstruktor mit optionalen Parametern
Verwendet als z
new Foo(5, b: new Bar(whatever))
. Nicht für Java- oder C # -Versionen vor 4.0. Es lohnt sich jedoch zu zeigen, dass nicht alle Lösungen sprachunabhängig sind.Option 2. Konstruktor, der ein einzelnes Parameterobjekt verwendet
Anwendungsbeispiel:
C # ab 3.0 macht dies mit der Objektinitialisierersyntax eleganter (semantisch äquivalent zum vorherigen Beispiel):
Option 3:
Gestalten Sie Ihre Klasse neu, um nicht so viele Parameter zu benötigen. Sie können die Verantwortlichkeiten in mehrere Klassen aufteilen. Oder übergeben Sie Parameter bei Bedarf nicht an den Konstruktor, sondern nur an bestimmte Methoden. Nicht immer lebensfähig, aber wenn es so ist, lohnt es sich.
quelle