Quali sono i lati negativi dell'implementazione di un singleton con l'enum di Java?

14

Tradizionalmente, un singleton viene solitamente implementato come

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

    public static Foo1 getInstance(){ return INSTANCE; }

    private Foo1(){}

    public void doo(){ ... }
}

Con enum di Java, possiamo implementare un singleton come

public enum Foo2
{
    INSTANCE;

    public void doo(){ ... }
}

Per quanto eccezionale sia la versione 2, ci sono degli svantaggi?

(Gli ho dato alcuni pensieri e risponderò alla mia domanda, spero che tu abbia risposte migliori)

    
posta irreputable 14.12.2012 - 01:39
fonte

3 risposte

31

Alcuni problemi con enum singletons:

Impegno per una strategia di implementazione

In genere, "singleton" si riferisce a una strategia di implementazione, non a una specifica API. È molto raro che Foo1.getInstance() dichiari pubblicamente che restituirà sempre la stessa istanza. Se necessario, l'implementazione di Foo1.getInstance() può evolvere, ad esempio, per restituire un'istanza per thread.

Con Foo2.INSTANCE dichiariamo pubblicamente che questa istanza è l'istanza e non c'è possibilità di cambiarla. La strategia di implementazione di avere una singola istanza è esposta e impegnata a.

Questo problema non è paralizzante. Ad esempio, Foo2.INSTANCE.doo() può contare su un oggetto helper locale del thread, per efficace avere un'istanza per-thread.

Estensione della classe Enum

Foo2 estende una super classe Enum<Foo2> . Di solito vogliamo evitare le super classi; specialmente in questo caso, la super classe forzata su Foo2 non ha nulla a che fare con ciò che Foo2 dovrebbe essere. Questo è un inquinamento per la gerarchia di tipi della nostra applicazione. Se vogliamo davvero una super classe, di solito è una classe di applicazione, ma non possiamo, la super classe di Foo2 è fissa.

Foo2 eredita alcuni divertenti metodi di istanza come name(), cardinal(), compareTo(Foo2) , che confondono solo gli utenti di Foo2 . Foo2 non può avere il proprio metodo name() anche se quel metodo è desiderabile nell'interfaccia Foo2 .

Foo2 contiene anche alcuni divertenti metodi statici

    public static Foo2[] values() { ... }
    public static Foo2 valueOf(String name) { ... }
    public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)

che sembra essere privo di senso per gli utenti. Solitamente un singleton non dovrebbe avere metodi statici pulbic (tranne il getInstance() )

serializzabilità

È molto comune che i singleton abbiano uno stato. Questi singleton generalmente dovrebbero non essere serializzabili. Non riesco a pensare ad alcun esempio realistico in cui abbia senso trasportare un singleton stateful da una VM ad un'altra VM; un singleton significa "unico all'interno di una VM", non "unico nell'universo".

Se la serializzazione ha realmente senso per un singleton stateful, il singleton dovrebbe specificare in modo esplicito e preciso cosa significa deserializzare un singleton in un'altra VM in cui potrebbe esistere già un singleton dello stesso tipo.

Foo2 si impegna automaticamente in una semplicistica strategia di serializzazione / deserializzazione. Questo è solo un incidente che aspetta di accadere. Se abbiamo un albero di dati che fa riferimento concettualmente a una variabile di stato di Foo2 in VM1 in t1, attraverso la serializzazione / deserializzazione il valore diventa un valore diverso - il valore della stessa variabile di Foo2 in VM2 in t2, creando un hard per rilevare bug. Questo bug non avverrà in modo silenzioso per Foo1 unserializable.

Restrizioni di codifica

Ci sono cose che possono essere fatte nelle classi normali, ma vietate nelle classi enum . Ad esempio, accedere a un campo statico nel costruttore. Il programmatore deve essere più attento dal momento che sta lavorando in una classe speciale.

Conclusione

Facendo piggyback su enum, salviamo 2 righe di codice; ma il prezzo è troppo alto, dobbiamo trasportare tutti i bagagli e le restrizioni delle enumerazioni, inavvertitamente ereditiamo "caratteristiche" di enum che hanno conseguenze non volute. L'unico presunto vantaggio - la serializzabilità automatica - risulta essere uno svantaggio.

    
risposta data 14.12.2012 - 02:56
fonte
6

Un'istanza di enum dipende dal programma di caricamento classi. vale a dire se hai un caricatore di seconda classe che non ha il caricatore di prima classe come genitore che carica la stessa classe di enumerazione puoi ottenere più istanze in memoria.

Esempio di codice

Crea il seguente enum e metti il suo file .class in un barattolo da solo. (ovviamente il barattolo avrà la struttura del pacchetto / cartella corretta)

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

Ora esegui questo test, assicurandoti che non ci siano copie dell'enumerazione precedente sul percorso della classe:

@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);
}

E ora abbiamo due oggetti che rappresentano lo "stesso" valore enum.

Sono d'accordo che questo è un caso d'angolo raro e inventato, e quasi sempre un enum può essere usato per un singleton java. Lo faccio da solo Ma la domanda posta su potenziali aspetti negativi e questa nota di cautela merita di essere presa in considerazione.

    
risposta data 09.07.2013 - 18:17
fonte
2

Il pattern enum non può essere usato per nessuna classe che genererebbe un'eccezione nel costruttore. Se necessario, utilizza una fabbrica:

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;
    }        
}
    
risposta data 23.04.2014 - 20:02
fonte

Leggi altre domande sui tag