Ich bin an die althergebrachte prozedurale C-Programmierung gewöhnt. Jetzt lerne ich Java und bin zu der vielleicht offensichtlichen Einsicht gekommen, dass das harte Bit Design ist und keine Syntax.
Ich habe Stunden damit verbracht, Idee für Idee, wie ich meinen Zoo bevölkern könnte, zu verschrotten:
Ich habe eine class Zoo
und eine abstrakte class Animal
. Es gibt mehrere nicht abstrakte Unterklassen von Animal
namens Lion
, Giraffe
, Zebra
, Penguin
und so weiter. Ein Zoo
-Objekt enthält genau eine Instanz jeder Unterklasse von Animal
, und jede dieser Instanzen enthält einen Verweis auf die eindeutige Zoo
-Instanz, zu der sie gehört.
Ich würde gerne in einer bestimmten Reihenfolge über die Tiere im Zoo iterieren (wenn Sie zum Beispiel über einen Fußweg gehen) und in einem Wörterbuch Tiere im Zoo nachschlagen. Genauer gesagt werde ich an einer bestimmten Stelle eine Textdatei analysieren, die Tiernamen enthält (z. B. für die Zeichenfolge "LION" möchte ich die eindeutige Lion
-Instanz erhalten). Es gibt eine Eins-zu-Eins-Zuordnung von Zeichenfolgen zu Tieren. Ich habe mich für LinkedHashMap<String, Animal>
entschieden.
Ich möchte überschaubaren Code schreiben, mit dem ich in Zukunft einfach mehr Tiere hinzufügen kann. Mein bisher bester Ansatz ist folgender:
In der Klasse Zoo
definiere ich ein enum
, das die Reihenfolge der Tiere widerspiegelt, die ich möchte, und seine Elemente entsprechen genau den Strings, die ich in der Textdatei analysieren werde.
In Zoo
Ich habe auch eine Methode, die ein Tierobjekt erstellt:
Als Teil des Konstruktors von Zoo
iteriere ich über den enum
und füge Elemente in den LinkedHashMap
namens animals
:
Um ein neues Tier hinzuzufügen, muss ich
Animal
, enum
, switch
-Anweisung in der makeAnimal(Species species)
-Methode einen Fall hinzu. Ist das ein vernünftiger Ansatz? Nun, nachdem ich mir die Zeit genommen habe, diese Frage zu formulieren, bin ich eigentlich ziemlich glücklich mit meiner Herangehensweise;), aber vielleicht fehlt mir ein offensichtliches Designmuster und meine Lösung wird irgendwann zurückschlagen. Ich habe das Gefühl, dass es eine unerwünschte Trennung zwischen dem Namen "LION"
und seinem class Lion
gibt, der nicht ideal ist.
Lesen Sie Ihre Frage Ich fand die folgenden Anforderungen von Zoo:
Wie Sie erwähnt haben, gibt es eine unerwünschte Trennung zwischen der Zeichenkette "LION"
und der Klasse Lion
. In Effektivem Java, Element 50, wird Folgendes zu Strings angegeben:
Strings sind ein schlechter Ersatz für andere Werttypen . Wenn ein Datenelement aus einer Datei, aus dem Netzwerk oder von einer Tastatureingabe in ein Programm kommt, ist es oft in Zeichenfolgenform. Es gibt eine natürliche Tendenz, es so zu belassen, aber diese Tendenz ist nur gerechtfertigt, wenn die Daten wirklich textueller Natur sind. Wenn es numerisch ist, sollte es in den entsprechenden numerischen Typ übersetzt werden, z. B.
int
,float
oderBigInteger
. Wenn es sich um eine Ja-oder-Nein-Frage handelt, sollte diese in einboolean
übersetzt werden. Allgemeiner, wenn es einen geeigneten Werttyp gibt, egal ob Grundelement oder Objektreferenz, sollten Sie ihn verwenden; Wenn nicht, sollten Sie einen schreiben. Während dieser Ratschlag offensichtlich erscheint, wird er oft verletzt.
Daher sollten Tiere nicht nach ihrer Spezies name gesucht werden, sondern nach ihrer Spezies instance gesucht werden.
Um die Reihenfolge der Arten zu definieren, müssen Sie eine Sammlung mit einer vorhersagbaren Iterationsreihenfolge verwenden, z. B. die erwähnte LinkedHashMap
.
Das Hinzufügen von Tieren besteht derzeit aus den drei Schritten, die Sie erwähnt haben. Ein Nebeneffekt der Verwendung einer Enumeration ist, dass nur eine Person mit Zugriff auf den Quellcode die Fähigkeit hat, neue Spezies hinzuzufügen (da die Species
enum erweitert werden muss).
Betrachten wir nun Species.LION
, das ist die Spezies der Klasse Lion
. Beachten Sie, dass diese Beziehung semantisch mit der Beziehung zwischen einer Klasse und ihrer Instanziierung identisch ist. Daher wäre eine viel elegantere Lösung, Lion.class
als die Spezies von Lion
zu verwenden. Dies reduziert auch die Anzahl der Schritte für das Hinzufügen eines Tieres, wie Sie die Spezies kostenlos erhalten.
In Ihrem Vorschlag hat der Zoo die Verantwortung, Tiere zu schaffen. Die Folge ist, dass jeder Zoo, der jemals erschaffen wurde, alle definierten Tiere verwenden muss (wegen der Species
enum) und die angegebene Reihenfolge verwenden würde, es würde keine Unterschiede zwischen den Zoos geben . Es ist besser, die Schaffung von Tieren vom Zoo zu entkoppeln, um mehr Flexibilität sowohl der Tiere als auch des Zoos zu ermöglichen.
Aufgrund ihrer Flexibilität sollten Schnittstellen gegenüber abstrakten Klassen bevorzugt werden. Wie in Effektivem Java, Punkt 18 erklärt:
Element 18. Bevorzugt Interfaces zu abstrakten Klassen
Die Programmiersprache Java bietet zwei Mechanismen zum Definieren eines Typs, der mehrere Implementierungen zulässt: Schnittstellen und abstrakte Klassen. Der offensichtlichste Unterschied zwischen den beiden Mechanismen besteht darin, dass abstrakte Klassen Implementierungen für einige Methoden enthalten dürfen, während dies für Schnittstellen nicht der Fall ist. Ein wichtigerer Unterschied besteht darin, dass zur Implementierung des von einer abstrakten Klasse definierten Typs eine Klasse eine Unterklasse der abstrakten Klasse sein muss. Jede Klasse, die alle erforderlichen Methoden definiert und den allgemeinen Vertrag befolgt, darf eine Schnittstelle implementieren, unabhängig davon, wo sich die Klasse in der Klassenhierarchie befindet. Da Java nur einzelne Vererbung zulässt, schränkt diese Beschränkung auf abstrakte Klassen ihre Verwendung als Typdefinitionen stark ein.
In Ihrer Frage erwähnen Sie, dass ein Tier auch einen Bezug zum Zoo haben sollte, in dem es sich befindet. Dies führt eine zirkuläre Referenz ein, die so weit wie möglich vermieden werden sollte.
Einer der vielen Nachteile von Zirkelverweisen ist:
Kreisförmige Klassenreferenzen erzeugen eine hohe Kopplung; Beide Klassen müssen bei jeder Änderung neu kompiliert werden.
Die obige Implementierung unterstützt mehrere Tierarten pro Spezies, da dies für einen Zoo sinnvoller erscheint. Wenn nur ein Tier benötigt wird, sollten Sie Guavas ClassToInstanceMap anstelle von SetMultimap verwenden.
Die Kreation der Tiere wurde nicht als Teil des Designproblems betrachtet. Wenn komplexere Tiere erstellt werden müssen, sollten Sie das Builder-Muster verwenden.
Das Hinzufügen eines Tieres ist jetzt so einfach wie das Erstellen einer neuen Klasse, die die Schnittstelle Animal
implementiert und sie dem Zoo hinzufügt.
Obwohl vielleicht eine Nebensache, sollte man darauf achten, unnötige zirkuläre Referenzen / Abhängigkeiten zu vermeiden. Ich möchte nicht den dogmatischen Standpunkt vertreten, dass alle Formen von zirkulären Abhängigkeiten schlecht sind, aber je nachdem, wie Sie Ihr Programm verwenden möchten, ist es besser, wenn Sie kein Tier brauchen, von dem ein Zoo und ein Zoo abhängig sind mehrere Tiere gleichzeitig.
In Ihrem Beispiel sind die Klassen Zoo und Animal vielleicht enger gekoppelt, als sie sein müssen. Wenn Sie ein Tier in einen anderen Zoo umziehen möchten, müssen Sie sowohl die Zoo-Instanz als auch die Tierinstanz aktualisieren. Wenn Sie die bidirektionale Navigation beibehalten möchten, können Sie z. eine bidirektionale Karte im Zoo, da der Zoo die Aufgabe hätte, Tiere zu summieren (was auch ein Zoo in der realen Welt tut), aber Zoo wäre dann auch der einzige Ort, an dem diese Beziehung aufrechterhalten wird. Meiner Meinung nach ist das sauberer.
Eine weitere Diskussion über den Punkt der Zirkelbezüge finden Sie hier: Ссылка
Ja, Ihre Vorgehensweise scheint korrekt zu sein.
Wenn Sie jedoch nur ein Tier nach Spezies haben, können Sie es vielleicht direkt in einem statischen Feld instanziieren, ohne dass Sie eine bestimmte Methode benötigen.
Sie können auch eine Factory-Klasse wie folgt hinzufügen:
%Vor%und implementiert eine Fabrik für jede Art von Tier:
%Vor%Und Sie können diese Factory auch direkt in Ihrem Enum hinzufügen:
%Vor%und dann, wenn Sie eine Giraffe wollen, müssen Sie nur anrufen:
%Vor%Insgesamt sieht Ihr Design gut aus. Ich werde auf einige Punkte eingehen, die Sie angesprochen haben:
Bestimmen Sie die Reihenfolge - Hier ist eine Option:
%Vor%Sie können das neue ConcreteAnimal () durch eine Methode ersetzen, die die Instanz erstellt. Etwas wie:
%Vor% List
(und ArrayList
) ist eine fortlaufende Liste. So ist die Einfüge- und Abrufreihenfolge garantiert gleich.
Außerdem können Sie Tierart sehr einfach hinzufügen / entfernen.
Das Designmuster, das Sie verwendet haben, ähnelt sehr dem Factory Design Pattern , das in die Kategorie der Kreatives Design-Muster. Diese Art von Entwurfsmustern beschäftigen sich mit der Kreation von Objekten.
Die Methode, welche die Objekte für Tiere makeAnimal(Species species)
erstellt, kann in eine separate Klasse gestellt werden, die im Wesentlichen etwas in der Reihenfolge von "AnimalFactory" oder ähnlichem heißen kann.
Abgesehen davon denke ich, dass dein Design gut ist.
Tags und Links java design-patterns design