¿Cuáles son las desventajas de implementar un singleton con la enumeración de Java?

14

Tradicionalmente, un singleton generalmente se implementa como

public class Foo1
{
    private static final Foo1 INSTANCE = new Foo1();

    public static Foo1 getInstance(){ return INSTANCE; }

    private Foo1(){}

    public void doo(){ ... }
}

Con la enumeración de Java, podemos implementar un singleton como

public enum Foo2
{
    INSTANCE;

    public void doo(){ ... }
}

Tan impresionante como es la segunda versión, ¿hay algún inconveniente?

(Le di algunas ideas y responderé mi propia pregunta; espero que tengas mejores respuestas)

    
pregunta irreputable 14.12.2012 - 01:39
fuente

3 respuestas

31

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.

    
respondido por el irreputable 14.12.2012 - 02:56
fuente
6

Una instancia de enum depende del cargador de clases. es decir, si tiene un cargador de segunda clase que no tiene el cargador de primera clase como padre que carga la misma clase enum, puede obtener varias instancias en la memoria.

Ejemplo de código

Cree la siguiente enumeración y coloque su archivo .class en un archivo jar por sí mismo. (por supuesto, el contenedor tendrá la estructura de paquete / carpeta correcta)

package mad;
public enum Side {
  RIGHT, LEFT;
}

Ahora ejecute esta prueba, asegurándose de que no haya copias de la enumeración anterior en la ruta de clases:

@Test
public void testEnums() throws Exception
{
    final ClassLoader root = MadTest.class.getClassLoader();

    final File jar = new File("path to jar"); // Edit path
    assertTrue(jar.exists());
    assertTrue(jar.isFile());

    final URL[] urls = new URL[] { jar.toURI().toURL() };
    final ClassLoader cl1 = new URLClassLoader(urls, root);
    final ClassLoader cl2 = new URLClassLoader(urls, root);

    final Class<?> sideClass1 = cl1.loadClass("mad.Side");
    final Class<?> sideClass2 = cl2.loadClass("mad.Side");

    assertNotSame(sideClass1, sideClass2);

    assertTrue(sideClass1.isEnum());
    assertTrue(sideClass2.isEnum());
    final Field f1 = sideClass1.getField("RIGHT");
    final Field f2 = sideClass2.getField("RIGHT");
    assertTrue(f1.isEnumConstant());
    assertTrue(f2.isEnumConstant());

    final Object right1 = f1.get(null);
    final Object right2 = f2.get(null);
    assertNotSame(right1, right2);
}

Y ahora tenemos dos objetos que representan el "mismo" valor de enumeración.

Estoy de acuerdo en que este es un caso de esquina raro y artificial, y casi siempre se puede usar una enumeración para un singleton de java. Lo hare yo mismo. Pero vale la pena conocer la pregunta sobre posibles desventajas y esta nota de precaución.

    
respondido por el Mad G 09.07.2013 - 18:17
fuente
2

el patrón de enumeración no se puede usar para ninguna clase que arrojaría una excepción en el constructor. Si es necesario, utilice una fábrica:

class Elvis {
    private static Elvis self = null;
    private int age;

    private Elvis() throws Exception {
        ...
    }

    public synchronized Elvis getInstance() throws Exception {
        return self != null ? self : (self = new Elvis());
    }

    public int getAge() {
        return age;
    }        
}
    
respondido por el user3158036 23.04.2014 - 20:02
fuente

Lea otras preguntas en las etiquetas