Ein Zoo-Objekt mit Tierobjekten mit Enum in Java auffüllen

8

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.

%Vor%

In Zoo Ich habe auch eine Methode, die ein Tierobjekt erstellt:

%Vor%

Als Teil des Konstruktors von Zoo iteriere ich über den enum und füge Elemente in den LinkedHashMap namens animals :

ein %Vor%

Um ein neues Tier hinzuzufügen, muss ich

  1. fügen Sie eine Unterklasse zu Animal ,
  2. hinzu
  3. Halte ein neues Element in enum ,
  4. fügen Sie der 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.

    
DustByte 14.01.2016, 15:49
quelle

5 Antworten

8

Anforderungen

Lesen Sie Ihre Frage Ich fand die folgenden Anforderungen von Zoo:

  1. Suche Tiere nach ihrem Speziesnamen
  2. Definieren Sie die Reihenfolge der Arten
  3. Fügen Sie in Zukunft einfach Tiere hinzu

1. Sucht Tiere nach ihrem Speziesnamen

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 oder BigInteger . Wenn es sich um eine Ja-oder-Nein-Frage handelt, sollte diese in ein boolean ü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.

2. Definieren Sie die Reihenfolge der Arten

Um die Reihenfolge der Arten zu definieren, müssen Sie eine Sammlung mit einer vorhersagbaren Iterationsreihenfolge verwenden, z. B. die erwähnte LinkedHashMap .

3. Einfach Tiere in der Zukunft hinzufügen

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.

Analyse anderer Teile des Codes

Zoo

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.

Tier

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.

Zirkulare Referenzen

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.

Beispielimplementierung

%Vor%

Verwendung

%Vor%

Diskussion

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.

    
rinde 17.01.2016, 17:18
quelle
5

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: Ссылка

    
Kristof Coninx 15.01.2016 10:21
quelle
4

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%     
Prim 14.01.2016 16:08
quelle
3

Insgesamt sieht Ihr Design gut aus. Ich werde auf einige Punkte eingehen, die Sie angesprochen haben:

  1. Ich schlage eine Schnittstelle vor, von der andere konkrete Tierklassen erben werden - damit Sie alle Tiere auf die gleiche Weise erreichen können.
  2. 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.

chenop 14.01.2016 21:34
quelle
2

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.

    
Pritam Banerjee 14.01.2016 15:59
quelle

Tags und Links