Traditionell wird ein Singleton normalerweise als implementiert
public class Foo1
{
private static final Foo1 INSTANCE = new Foo1();
public static Foo1 getInstance(){ return INSTANCE; }
private Foo1(){}
public void doo(){ ... }
}
Mit Javas Enum können wir einen Singleton als implementieren
public enum Foo2
{
INSTANCE;
public void doo(){ ... }
}
Gibt es bei der 2. Version auch Nachteile?
(Ich habe darüber nachgedacht und beantworte meine eigene Frage. Hoffentlich hast du bessere Antworten.)
Antworten:
Einige Probleme mit Enum Singletons:
Bekenntnis zu einer Umsetzungsstrategie
In der Regel bezieht sich "Singleton" auf eine Implementierungsstrategie und nicht auf eine API-Spezifikation. Es ist sehr selten
Foo1.getInstance()
, dass öffentlich deklariert wird, dass immer dieselbe Instanz zurückgegeben wird. Bei Bedarf kann die Implementierung vonFoo1.getInstance()
so weiterentwickelt werden, dass eine Instanz pro Thread zurückgegeben wird.Mit
Foo2.INSTANCE
uns erklären öffentlich , dass diese Instanz ist die Instanz ist, und es gibt keine Chance, das zu ändern. Die Implementierungsstrategie, eine einzelne Instanz zu haben, wird offengelegt und begangen.Dieses Problem lähmt nicht. Beispielsweise
Foo2.INSTANCE.doo()
kann auf ein Thread-lokales Hilfsobjekt zurückgegriffen werden, um effektiv zu arbeiten eine Instanz pro Thread zu haben.Erweiterung der Enum-Klasse
Foo2
erweitert eine super klasseEnum<Foo2>
. Wir wollen normalerweise Superklassen vermeiden; gerade in diesem Fall hat die aufgezwungeneFoo2
Superklasse nichts mit dem zu tun, was seinFoo2
soll. Das ist eine Verschmutzung der Typenhierarchie unserer Anwendung. Wenn wir wirklich eine Superklasse wollen, ist es normalerweise eine Anwendungsklasse, aber wir können nicht,Foo2
die Superklasse ist behoben.Foo2
erbt einige lustige Instanzmethoden wiename(), cardinal(), compareTo(Foo2)
, die dieFoo2
Benutzer nur verwirren .Foo2
Ich kann keine eigenename()
Methode haben, auch wenn diese Methode inFoo2
der Benutzeroberfläche wünschenswert ist .Foo2
enthält auch einige lustige statische Methodenwas für Benutzer unsinnig erscheint. Ein Singleton sollte normalerweise sowieso keine pulbischen statischen Methoden haben (außer der
getInstance()
)Serialisierbarkeit
Singletons sind sehr häufig staatlich. Diese Singletons sollten im Allgemeinen nicht serialisierbar sein. Ich kann mir kein realistisches Beispiel vorstellen, bei dem es sinnvoll ist, einen statusbehafteten Singleton von einer VM zu einer anderen zu transportieren. Ein Singleton bedeutet "einzigartig innerhalb einer VM", nicht "einzigartig im Universum".
Wenn die Serialisierung für ein statusbehaftetes Singleton wirklich Sinn macht, sollte das Singleton explizit und genau angeben, was es bedeutet, ein Singleton in einer anderen VM zu deserialisieren, in der möglicherweise bereits ein Singleton desselben Typs vorhanden ist.
Foo2
legt automatisch eine vereinfachte Serialisierungs- / Deserialisierungsstrategie fest. Das ist nur ein Unfall, der darauf wartet, passiert zu werden. Wenn wir einenFoo2
Datenbaum haben, der konzeptionell auf eine Zustandsvariable von in VM1 zu t1 verweist, wird der Wert durch Serialisierung / Deserialisierung zu einem anderen Wert - der Wert derselben Variablen vonFoo2
in VM2 zu t2, wodurch ein schwer zu erkennender Fehler entsteht. Dieser Fehler tritt nicht bei den unserializable aufFoo1
.Einschränkungen der Codierung
Es gibt Dinge, die in normalen Klassen gemacht werden können, aber in
enum
Klassen verboten sind. Zum Beispiel auf ein statisches Feld im Konstruktor zugreifen. Der Programmierer muss vorsichtiger sein, da er in einer speziellen Klasse arbeitet.Fazit
Durch Huckepack auf Enum sparen wir 2 Codezeilen; aber der Preis ist zu hoch, wir müssen alle Gepäckstücke und Beschränkungen von Aufzählungen tragen, wir erben versehentlich "Merkmale" von Aufzählungen, die unbeabsichtigte Konsequenzen haben. Der einzige vermeintliche Vorteil - die automatische Serialisierbarkeit - erweist sich als Nachteil.
quelle
Constructor<?> c=EnumType.class.getDeclaredConstructors()[0]; c.setAccessible(true); EnumType f=(EnumType)MethodHandles.lookup().unreflectConstructor(c).invokeExact("BAR", 1);
zB ein wirklich schönes Beispiel wäre :;Constructor<?> c=Thread.State.class.getDeclaredConstructors()[0]; c.setAccessible(true); Thread.State f=(Thread.State)MethodHandles.lookup().unreflectConstructor(c).invokeExact("RUNNING_BACKWARD", -1);
^), getestet unter Java 7 und Java 8…Eine Enum-Instanz ist vom Klassenladeprogramm abhängig. dh wenn Sie einen Loader der zweiten Klasse haben, der nicht den Loader der ersten Klasse als übergeordnetes Element hat, das dieselbe Aufzählungsklasse lädt, können Sie mehrere Instanzen im Speicher abrufen.
Codebeispiel
Erstellen Sie die folgende Aufzählung, und legen Sie die zugehörige .class-Datei selbst in einem Glas ab. (Natürlich hat das Glas die richtige Paket- / Ordnerstruktur)
Führen Sie nun diesen Test aus, und stellen Sie sicher, dass der Klassenpfad keine Kopien der obigen Aufzählung enthält:
Und wir haben jetzt zwei Objekte, die den gleichen Enum-Wert darstellen.
Ich bin damit einverstanden, dass dies ein seltener und erfundener Eckfall ist und fast immer eine Aufzählung für einen Java-Singleton verwendet werden kann. Ich mache es selber. Aber die Frage nach möglichen Nachteilen und dieser Hinweis zur Vorsicht ist es wert, darüber informiert zu werden.
quelle
Das enum-Muster kann nicht für Klassen verwendet werden, die eine Ausnahme im Konstruktor auslösen würden. Wenn dies erforderlich ist, verwenden Sie eine Fabrik:
quelle