2016-01-06 9 views
5

Questa domanda è correlata ad Android ma può anche essere richiesta in altre situazioni. Ho bisogno di creare una biblioteca dove sono esposti i singleton; o voglio assicurarmi che esista solo una istanza delle mie classi e che possa essere afferrata ovunque nel codice senza passare dei riferimenti.Come implementare un singleton configurabile?

Ma questi singleton hanno bisogno di alcuni parametri. Ad esempio in Android, è spesso necessario un oggetto Context. Devo anche precisare che, dal momento che fornisco una libreria, voglio che le cose siano facili per l'utente e Non ho il controllo della classe Application in Android (questa classe può talvolta essere utilizzata per gestire le istanze dell'oggetto per un'intera app) .

Una soluzione nota è quella di effettuare le seguenti operazioni:

static MySingleton sInstance; 
MySingleton.getInstance(Context c) { 
    if (sInstance == null) { 
     sInstance = new MySingleton(c.getApplicationContext()); 
    } 
    return sInstance; 
} 

Ma è strano dato che il parametro di getInstance è effettivamente utilizzato solo alla prima creazione del Singleton.

Potrei fornire un setter per il singleton e chiede agli sviluppatori di impostare correttamente i parametri necessari, ma potrebbero sorgere alcune situazioni strane:

// Good usage 
MySingleton.getInstance().setContext(context.getApplicationContext()); 
MySingleton.getInstance().doSomethingThatRequiresContext(); // OK 

// Bad usage 
MySingleton.getInstance().doSomethingThatRequiresContext(); // Error! 
MySingleton.getInstance().setContext(context.getApplicationContext()); 

ho potuto verificare all'inizio di ciascun metodo se il Singleton è configurati correttamente e lanciare alcune eccezioni in caso di cattivo stato, ma l'API è meno semplice da usare:

MySingleton.getInstance().setContext(context.getApplicationContext()); 

try { 
    MySingleton.getInstance().doSomethingThatRequiresContext(); 
} 
catch(BadSingletonConfiguration e) { } 

Anche se io uso le eccezioni di runtime, sarebbe pericoloso per l'uso.

A meno che non chieda gentilmente all'utente di creare manualmente l'istanza e assicurarsi che solo un'istanza esisterà, non vedo una buona soluzione.

+0

magari utilizzare oggetti immutabili .... Java String viene implementato per essere immutabile. – JFPicard

+0

Si noti che ognuno dei seguenti esempi presenta una perdita di memoria se il singleton viene fatto riferimento all'interno di un thread. Devi usare WeakReference per memorizzare un contesto, altrimenti il ​​contesto non sarà GC quando dovrebbe essere altrimenti. – mawalker

+0

Se si scrive sul contesto, di solito viene eseguito un getApplicationContext() per avere un contesto che comprenda l'intero ciclo di vita dell'attività, quindi non viene eseguito GC mentre l'app è in diretta. Posso modificare gli esempi, se ti va. –

risposta

1

ne dite di fare questo, invece:

MySingleton.getInstance().doSomethingThatRequiresContext(context); 

Ma in generale è meglio usare approccio iniezione di dipendenza invece di utilizzare single creati manualmente. Potresti voler dare un'occhiata a Dagger.

+0

Non possibile poiché l'oggetto contesto non è disponibile ovunque e altri metodi lo richiedono. Dagger avrebbe senso se controllo l'app principale, ma non è il caso, fornisco solo una libreria. –

+0

Ho difficoltà a trovare un esempio quando non è possibile ottenere il contesto. –

+0

Hai ragione, ma voglio generalizzare la mia domanda con altri possibili oggetti di configurazione, non solo il contesto. Ad ogni modo, se tutti i miei metodi hanno bisogno di un'istanza di contesto e se devo passarlo a ciascun metodo, iniziamo a spostarci da OOP a uno stile più procedurale dove non è possibile incapsulare lo stato dell'oggetto :-) –

2

Si potrebbe avere un metodo createInstance che accetta un contesto e un getInstance che restituiscono null o generano un'eccezione significativa se chiamano getInstance prima di creare un'istanza. Magari lancia una RuntimeException affermando che createInstance deve essere chiamato prima.

Inoltre, createInstance restituirà solo l'istanza già creata se è già stata chiamata. Ecco un esempio di codice di quello che sto pensando:

public class MySingleton 
{ 
    private static MySingleton INSTANCE; 
    private final Context context; 

    public static MySingleton createInstance(Context context) 
    { 
     if(INSTANCE == null) 
     { 
      INSTANCE = new MySingleton(context); 
     } 
     return INSTANCE; 
    } 

    public static MySingleton getInstance() 
    { 
     if(INSTANCE == null) 
     { 
      throw new RuntimeException("You must call createInstance first"); 
     } 
     return INSTANCE; 
    } 

    public void doSomethingThatRequiresContext() 
    { 
     context.doSomething(); 
    } 

    private MySingleton(Context context) 
    { 
     this.context = context; 
    } 
} 
+0

Sfortunatamente è molto simile al mio ultimo esempio. Le eccezioni sono tuttavia più centralizzate rispetto a un solo metodo. –

+1

L'utente deve passare le dipendenze nel costruttore, a cui non possiamo consentire l'accesso, o in ogni chiamata di metodo. Il metodo createInstance è un po 'come il costruttore qui, devono costruire il tuo oggetto da qualche parte. Penso che sarebbe abbastanza facile dire che è necessario chiamare createInstance se si chiama accidentalmente getInstance a causa dell'eccezione. –

+0

Questa è praticamente l'opzione più pulita a tua disposizione, ma memorizza riferimenti di contesto con "WeakReference", altrimenti ti imbatterai in problemi con la generazione di perdite di memoria quando il garbage collector non può essere eseguito. Questo accadrà con qualsiasi tipo di elaborazione in background. – mawalker

1

Un'alternativa è quella di restituire un'istanza da un metodo statico init, e hanno tutti i metodi sulla istanza restituita.

MyInstance instance = MyLibrary.init(context, and, whatever, else); 
instance.doSomethingThatRequiresContext(); 

L'ordine delle chiamate non può essere invertito ora.

Quindi tutto ciò che potrebbe potenzialmente essere necessario evitare è chiamare init due volte. Che si possa fare con l'eccezione di runtime, o restituire l'istanza precedente, personalmente vorrei Runtime se init viene chiamato due volte.

Voglio assicurarmi che esista solo una istanza delle mie classi e che possa essere afferrata ovunque nel codice senza passare dei riferimenti.

Fornire un getInstance che è valida solo dopo una chiamata al init

//MyLibrary.getInstance() would throw before init 
MyInstance instance1 = MyLibrary.init(context, and, whatever, else); 
MyInstance instance2 = MyLibrary.getInstance(); 
assertSame(instance1, instance2); 

Si noti che anche se solo leggermente diverso al vostro originale, separando le responsabilità di assegnazione e gestione Singleton a MyLibrary, almeno solo i metodi init e getInstance devono verificare che init sia stato chiamato o meno. Nessuno dei metodi su MyInstance deve preoccuparsi.

Anche se utilizzo le eccezioni del runtime, sarebbe pericoloso da utilizzare.

Non penso che non sia possibile risolvere questo problema senza di loro. È più pericoloso non gettare quando qualcosa è seriamente sbagliato, come l'utente non è stato inizializzato. Basta aggiungere un messaggio di errore per eseguire il backup della documentazione.

integrale:

public final class MyInstance { 

    private final Context context; 

    MyInstance(Context context, and, whatever, else) { 
     //package private, can't be directly instantiated by library user 
     this.context = context; 
    } 

    public void doSomethingThatRequiresContext() { 
     //no special checks required 
    } 

    //add other methods, this is just a normal class 
} 

public static class MyLibrary { 

    private static Object lockObj = new Object(); 

    private static MyInstance myInstance; 

    public static MyInstance init(context, and, whatever, else) { 
     synchronized(lockObj) { 
      if (myInstance != null) 
       throw new RuntimeException("Please do not call init more than once"); 
      myInstance = new MyInstance(context, and, whatever, else); 
      return myInstance; 
     } 
    } 

    public static MyInstance getInstance() { 
     synchronized(lockObj) { 
      if (myInstance == null) 
       throw new RuntimeException("Please call init before getInstance"); 
      return myInstance; 
     } 
    } 

}