Verwenden von enum zum Implementieren von Multitons in Java

9

Ich hätte gerne einen begrenzten festen Katalog von Instanzen einer bestimmten komplexen Schnittstelle. Das Standardmuster multiton hat einige nette Funktionen, wie zum Beispiel faule Instanziierung. Es beruht jedoch auf einem Schlüssel wie einem String, der ziemlich fehleranfällig und zerbrechlich erscheint.

Ich hätte gerne ein Muster, das enum verwendet. Sie haben viele tolle Funktionen und sind robust. Ich habe versucht, ein Standarddesignmuster dafür zu finden, aber habe ein Leerzeichen gezeichnet. Also habe ich mir meine eigene ausgedacht, aber ich bin nicht sehr glücklich damit.

Das Muster, das ich verwende, ist wie folgt (die Schnittstelle ist hier stark vereinfacht, um es lesbar zu machen):

%Vor%

Dieses Muster hat einige sehr schöne Eigenschaften:

  • Die enum implementiert die Schnittstelle, die ihre Verwendung ziemlich natürlich macht: ComplexItem.ITEM1.method ();
  • Faule Instanziierung: Wenn die Konstruktion teuer ist (mein Anwendungsfall beinhaltet das Lesen von Dateien), tritt sie nur auf, wenn sie benötigt wird.

Nachdem es gesagt wurde, scheint es für eine so einfache Anforderung schrecklich komplex und "hacky" zu sein und überschreibt die Enum-Methoden in einer Weise, von der die Sprachdesigner nicht überzeugt sind.

Es hat auch einen anderen signifikanten Nachteil. In meinem Anwendungsfall möchte ich, dass die Schnittstelle vergleichbar ist. Leider kollidiert das dann mit der enum-Implementierung von Comparable und macht den Code nicht kompilierbar.

Eine Alternative, die ich in Betracht gezogen habe, war eine Standard-Enumeration und dann eine separate Klasse, die die Enumeration einer Implementierung der Schnittstelle zuordnet (unter Verwendung des Standard-Multiton-Musters). Das funktioniert, aber das Enum implementiert nicht mehr die Schnittstelle, die für mich keine natürliche Reflexion der Absicht zu sein scheint. Es trennt auch die Implementierung der Schnittstelle von den Enum-Elementen, die eine schlechte Kapselung zu sein scheint.

Eine weitere Alternative besteht darin, dass der Enum-Konstruktor die Schnittstelle implementiert (d. h. in dem obigen Muster entfernen Sie die Methode 'makeInstance'). Während dies funktioniert, entfernt es den Vorteil, nur die Konstruktoren auszuführen, falls erforderlich. Das Problem wird durch die Erweiterung Comparable ebenfalls nicht gelöst.

Meine Frage ist also: Kann mir jemand einen eleganteren Weg vorstellen, dies zu tun?

Als Antwort auf die Kommentare werde ich versuchen, das spezifische Problem zu spezifizieren, das ich zuerst generisch und dann anhand eines Beispiels zu lösen versuche.

  • Es gibt eine feste Menge von Objekten, die eine bestimmte Schnittstelle implementieren
  • Die Objekte sind zustandslos: Sie werden verwendet, um nur Verhalten zu verkapseln
  • Bei jeder Ausführung des Codes wird nur eine Teilmenge der Objekte verwendet (abhängig von der Benutzereingabe)
  • Das Erstellen dieser Objekte ist teuer: Es sollte nur einmal und nur bei Bedarf durchgeführt werden
  • Die Objekte teilen viel Verhalten

Dies könnte mit separaten Singleton-Klassen für jedes Objekt implementiert werden, wobei separate Klassen oder Oberklassen für gemeinsames Verhalten verwendet werden. Dies erscheint unnötig komplex.

Jetzt ein Beispiel. Ein System berechnet mehrere verschiedene Steuern in einer Gruppe von Regionen, von denen jede ihren eigenen Algorithmus zum Berechnen der Steuern hat. Es wird erwartet, dass sich die Anzahl der Regionen nie ändert, aber die regionalen Algorithmen werden sich regelmäßig ändern. Die spezifischen Regionaltarife müssen zur Laufzeit per Remote-Service geladen werden, was langsam und teuer ist. Jedes Mal, wenn das System aufgerufen wird, erhält es einen anderen Satz von zu berechnenden Regionen, so dass es nur die Raten der angeforderten Regionen laden sollte.

Also:

%Vor%

Empfohlener Hintergrund: Ein Enum einbinden

    
sprinter 17.09.2014, 23:01
quelle

3 Antworten

1

Das funktioniert für mich - es ist Thread-sicher und generisch. Die enum muss die Creator Schnittstelle implementieren, aber das ist einfach - wie die Beispielnutzung am Ende zeigt.

Diese Lösung unterbricht die Bindung, die Sie auferlegt haben, wobei es sich bei enum um das gespeicherte Objekt handelt. Hier verwende ich nur die enum als Factory, um das Objekt zu erstellen - auf diese Weise kann ich jede Art von Objekt speichern und jedes enum einen anderen Objekttyp erstellen (was mein Ziel war).

Dies verwendet einen gemeinsamen Mechanismus für Thread-Sicherheit und faule Instantiierung mit ConcurrentMap von FutureTask .

Es gibt einen kleinen Overhead, der die FutureTask für die Lebensdauer des Programms festhält, aber das könnte mit ein wenig Feinabstimmung verbessert werden.

%Vor%

In Java 8 ist es noch einfacher:

%Vor%     
OldCurmudgeon 18.09.2014, 08:47
quelle
2

Scheint gut. Ich würde die Thread-Initialisierung so machen:

%Vor%

Der Modifikator synchronized erscheint nur bei der createInstance() -Methode, aber umschließt den Aufruf von makeInstance() - vermittelt Thread-Sicherheit, ohne einen Engpass bei Aufrufen von getInstance() zu verursachen und ohne dass der Programmierer daran denken muss, synchronized hinzuzufügen zu jeder makeInstance() Implementierung.

    
Bohemian 18.09.2014 03:13
quelle
1

Man hat über dieses Muster nachgedacht: Die faule Instanziierung ist nicht threadsicher. Dies kann oder kann nicht in Ordnung sein, es hängt davon ab, wie Sie es verwenden möchten, aber es ist wissenswert. (In Anbetracht dessen, dass die Enum-Initialisierung an sich Thread-sicher ist.)

Abgesehen davon kann ich keine einfachere Lösung sehen, die eine vollständige Kontrolle der Instanz garantiert, intuitiv ist und faule Instanziierung verwendet.

Ich denke auch nicht, dass es ein Missbrauch von Enum-Methoden ist, es unterscheidet sich nicht viel von dem, was Josh Blochs Effective Java für die Kodierung verschiedener Strategien in Enums empfiehlt.

    
biziclop 17.09.2014 23:08
quelle

Tags und Links