Algunos problemas con enlet singletons:
Comprometiéndose con una estrategia de implementación
Normalmente, "singleton" se refiere a una estrategia de implementación, no a una especificación de API. Es muy raro que Foo1.getInstance()
declare públicamente que siempre devolverá la misma instancia. Si es necesario, la implementación de Foo1.getInstance()
puede evolucionar, por ejemplo, para devolver una instancia por subproceso.
Con Foo2.INSTANCE
declaramos públicamente que esta instancia es la , y no hay posibilidad de cambiar eso. La estrategia de implementación de tener una sola instancia está expuesta y comprometida.
Este problema no está paralizando. Por ejemplo, Foo2.INSTANCE.doo()
puede confiar en un objeto auxiliar local de subprocesos, para efectivamente tener una instancia por subproceso.
Extendiendo la clase Enum
Foo2
extiende una súper clase Enum<Foo2>
. Usualmente queremos evitar las súper clases; especialmente en este caso, la superclase forzada en Foo2
no tiene nada que ver con lo que se supone que Foo2
es. Eso es una contaminación para la jerarquía de tipos de nuestra aplicación. Si realmente queremos una súper clase, normalmente es una clase de aplicación, pero no podemos, la súper clase de Foo2
está corregida.
Foo2
hereda algunos métodos de instancia divertidos como name(), cardinal(), compareTo(Foo2)
, que son simplemente confusos para los usuarios de Foo2
. Foo2
no puede tener su propio método name()
incluso si ese método es deseable en la interfaz de Foo2
.
Foo2
también contiene algunos métodos estáticos divertidos
public static Foo2[] values() { ... }
public static Foo2 valueOf(String name) { ... }
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
que parece no tener sentido para los usuarios. Un singleton por lo general no debería tener métodos estáticos pulbic de todos modos (excepto el getInstance()
)
Serializabilidad
Es muy común que los singletons sean con estado. Estos singletons generalmente no deben ser serializables. No puedo pensar en ningún ejemplo realista en el que tenga sentido transportar un singleton con estado de una VM a otra VM; un singleton significa "único dentro de una VM", no "único en el universo".
Si la serialización realmente tiene sentido para un singleton con estado, el singleton debe especificar explícita y precisamente qué significa deserializar un singleton en otra máquina virtual donde puede existir un singleton del mismo tipo.
Foo2
se compromete automáticamente con una estrategia simplista de serialización / deserialización. Eso es solo un accidente esperando a suceder. Si tenemos un árbol de datos que hace referencia conceptualmente a una variable de estado de Foo2
en VM1 en t1, a través de la serialización / deserialización el valor se convierte en un valor diferente: el valor de la misma variable de Foo2
en VM2 en t2, creando una para detectar errores. Este error no se producirá en el Foo1
unserializable en silencio.
Restricciones de codificación
Hay cosas que se pueden hacer en clases normales, pero que están prohibidas en las clases enum
. Por ejemplo, accediendo a un campo estático en el constructor. El programador debe tener más cuidado ya que está trabajando en una clase especial.
Conclusión
Al llevar a cuestas la enumeración, guardamos 2 líneas de código; pero el precio es demasiado alto, tenemos que cargar con todos los equipajes y restricciones de las enumeraciones, heredamos inadvertidamente "características" de las enumeraciones que tienen consecuencias no deseadas. La única supuesta ventaja, la serialización automática, resulta ser una desventaja.