Singleton con argomenti in Java

Stavo leggendo l’articolo Singleton su Wikipedia e ho trovato questo esempio:

public class Singleton { // Private constructor prevents instantiation from other classs private Singleton() {} /** * SingletonHolder is loaded on the first execution of Singleton.getInstance() * or the first access to SingletonHolder.INSTANCE, not before. */ private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } 

Mentre mi piace molto il modo in cui si comporta questo Singleton, non riesco a vedere come adattarlo per incorporare argomenti al costruttore. Qual è il modo preferito per farlo in Java? Dovrei fare qualcosa di simile?

 public class Singleton { private static Singleton singleton = null; private final int x; private Singleton(int x) { this.x = x; } public synchronized static Singleton getInstance(int x) { if(singleton == null) singleton = new Singleton(x); return singleton; } } 

Grazie!


Edit: Penso di aver iniziato una tempesta di polemiche con il mio desiderio di usare Singleton. Lascia che ti spieghi la mia motivazione e spero che qualcuno possa suggerire un’idea migliore. Sto usando un framework di grid computing per eseguire attività in parallelo. In generale, ho qualcosa di simile a questo:

 // AbstractTask implements Serializable public class Task extends AbstractTask { private final ReferenceToReallyBigObject object; public Task(ReferenceToReallyBigObject object) { this.object = object; } public void run() { // Do some stuff with the object (which is immutable). } } 

Quello che succede è che anche se passo semplicemente un riferimento ai miei dati a tutte le attività, quando le attività sono serializzate, i dati vengono copiati più e più volte. Quello che voglio fare è condividere l’object tra tutte le attività. Naturalmente, potrei modificare la class in questo modo:

 // AbstractTask implements Serializable public class Task extends AbstractTask { private static ReferenceToReallyBigObject object = null; private final String filePath; public Task(String filePath) { this.filePath = filePath; } public void run() { synchronized(this) { if(object == null) { ObjectReader reader = new ObjectReader(filePath); object = reader.read(); } } // Do some stuff with the object (which is immutable). } } 

Come puoi vedere, anche qui ho il problema che passare un percorso di file diverso non significa nulla dopo che il primo è passato. Questo è il motivo per cui mi piace l’idea di un negozio che è stato pubblicato nelle risposte. Comunque, piuttosto che includere la logica per caricare il file nel metodo run, ho voluto astrarre questa logica in una class Singleton. Non fornirò ancora un altro esempio, ma spero che tu abbia l’idea. Per favore fammi sentire le tue idee per un modo più elegante per realizzare ciò che sto cercando di fare. Grazie ancora!

Farò il mio punto molto chiaro: un singleton con parametri non è un singleton .

Un singleton, per definizione, è un object che vuoi essere istanziato non più di una volta. Se stai cercando di alimentare i parametri per il costruttore, qual è il punto del singleton?

Hai due opzioni. Se vuoi che il tuo singleton venga inizializzato con alcuni dati, puoi caricarlo con i dati dopo l’istanza , in questo modo:

 SingletonObj singleton = SingletonObj.getInstance(); singleton.init(paramA, paramB); // init the object with data 

Se l’operazione che sta eseguendo il singleton è ricorrente e ogni volta con parametri diversi, è ansible passare i parametri al metodo principale in esecuzione:

 SingletonObj singleton = SingletonObj.getInstance(); singleton.doSomething(paramA, paramB); // pass parameters on execution 

In ogni caso, l’istanziazione sarà sempre parametrica. Altrimenti il ​​tuo singleton non è un singleton.

Penso che tu abbia bisogno di qualcosa come una fabbrica per avere oggetti con vari parametri istanziati e riutilizzati. Potrebbe essere implementato utilizzando una HashMap sincronizzata o ConcurrentHashMap mappare un parametro (un Integer per un esempio) alla class parametrizzabile ‘singleton’.

Sebbene tu possa arrivare al punto in cui dovresti usare classi regolari, non singleton (ad esempio, necessitando di 10.000 singleton diversamente parametrizzati).

Ecco un esempio per tale negozio:

 public final class UsefulObjFactory { private static Map store = new HashMap(); public static final class UsefulObj { private UsefulObj(int parameter) { // init } public void someUsefulMethod() { // some useful operation } } public static UsefulObj get(int parameter) { synchronized (store) { UsefulObj result = store.get(parameter); if (result == null) { result = new UsefulObj(parameter); store.put(parameter, result); } return result; } } } 

Per spingerlo ulteriormente, le enum Java possono anche essere considerate (o utilizzate come) singleton parametrizzati, sebbene consentano solo varianti statiche a numero fisso.

Tuttavia, se hai bisogno di una soluzione distribuita 1 , considera una soluzione di caching laterale. Ad esempio: EHCache, Terracotta, ecc.

1 nel senso di spanning multiple VM su probabilmente più computer.

Puoi anche usare il modello Builder se vuoi mostrare che alcuni parametri sono obbligatori.

  public enum EnumSingleton { INSTANCE; private String name; // Mandatory private Double age = null; // Not Mandatory private void build(SingletonBuilder builder) { this.name = builder.name; this.age = builder.age; } // Static getter public static EnumSingleton getSingleton() { return INSTANCE; } public void print() { System.out.println("Name "+name + ", age: "+age); } public static class SingletonBuilder { private final String name; // Mandatory private Double age = null; // Not Mandatory private SingletonBuilder(){ name = null; } SingletonBuilder(String name) { this.name = name; } public SingletonBuilder age(double age) { this.age = age; return this; } public void build(){ EnumSingleton.INSTANCE.build(this); } } } 

Quindi puoi creare / istanziare / parametrizzare come segue:

 public static void main(String[] args) { new EnumSingleton.SingletonBuilder("nico").age(41).build(); EnumSingleton.getSingleton().print(); } 

Usa getter e setter per impostare la variabile e rendere privato il costruttore predefinito. Quindi utilizzare:

 Singleton.getInstance().setX(value); 

Sorpreso che nessuno ha menzionato come viene creato / recuperato un logger. Ad esempio, di seguito viene mostrato come viene recuperato il logger Log4J .

 // Retrieve a logger named according to the value of the name parameter. If the named logger already exists, then the existing instance will be returned. Otherwise, a new instance is created. public static Logger getLogger(String name) 

Ci sono alcuni livelli di riferimento indiretto, ma la parte chiave è al di sotto del metodo che praticamente racconta tutto su come funziona. Usa una tabella hash per memorizzare i logger uscenti e la chiave è derivata dal nome. Se il logger non esiste per un nome give, utilizza una factory per creare il logger e quindi lo aggiunge alla tabella hash.

 69 Hashtable ht; ... 258 public 259 Logger getLogger(String name, LoggerFactory factory) { 260 //System.out.println("getInstance("+name+") called."); 261 CategoryKey key = new CategoryKey(name); 262 // Synchronize to prevent write conflicts. Read conflicts (in 263 // getChainedLevel method) are possible only if variable 264 // assignments are non-atomic. 265 Logger logger; 266 267 synchronized(ht) { 268 Object o = ht.get(key); 269 if(o == null) { 270 logger = factory.makeNewLoggerInstance(name); 271 logger.setHierarchy(this); 272 ht.put(key, logger); 273 updateParents(logger); 274 return logger; 275 } else if(o instanceof Logger) { 276 return (Logger) o; 277 } ... 

Modifica del pattern Singleton che utilizza l’inizializzazione di Bill Pugh sull’idioma del detentore della domanda . Questo è thread-safe senza il sovraccarico di costrutti linguistici specializzati (cioè volatili o sincronizzati):

 public final class RInterfaceHL { /** * Private constructor prevents instantiation from other classs. */ private RInterfaceHL() { } /** * R REPL (read-evaluate-parse loop) handler. */ private static RMainLoopCallbacks rloopHandler = null; /** * SingletonHolder is loaded, and the static initializer executed, * on the first execution of Singleton.getInstance() or the first * access to SingletonHolder.INSTANCE, not before. */ private static final class SingletonHolder { /** * Singleton instance, with static initializer. */ private static final RInterfaceHL INSTANCE = initRInterfaceHL(); /** * Initialize RInterfaceHL singleton instance using rLoopHandler from * outer class. * * @return RInterfaceHL instance */ private static RInterfaceHL initRInterfaceHL() { try { return new RInterfaceHL(rloopHandler); } catch (REngineException e) { // a static initializer cannot throw exceptions // but it can throw an ExceptionInInitializerError throw new ExceptionInInitializerError(e); } } /** * Prevent instantiation. */ private SingletonHolder() { } /** * Get singleton RInterfaceHL. * * @return RInterfaceHL singleton. */ public static RInterfaceHL getInstance() { return SingletonHolder.INSTANCE; } } /** * Return the singleton instance of RInterfaceHL. Only the first call to * this will establish the rloopHandler. * * @param rloopHandler * R REPL handler supplied by client. * @return RInterfaceHL singleton instance * @throws REngineException * if REngine cannot be created */ public static RInterfaceHL getInstance(RMainLoopCallbacks rloopHandler) throws REngineException { RInterfaceHL.rloopHandler = rloopHandler; RInterfaceHL instance = null; try { instance = SingletonHolder.getInstance(); } catch (ExceptionInInitializerError e) { // rethrow exception that occurred in the initializer // so our caller can deal with it Throwable exceptionInInit = e.getCause(); throw new REngineException(null, exceptionInInit.getMessage()); } return instance; } /** * org.rosuda.REngine.REngine high level R interface. */ private REngine rosudaEngine = null; /** * Construct new RInterfaceHL. Only ever gets called once by * {@link SingletonHolder.initRInterfaceHL}. * * @param rloopHandler * R REPL handler supplied by client. * @throws REngineException * if R cannot be loaded. */ private RInterfaceHL(RMainLoopCallbacks rloopHandler) throws REngineException { // tell Rengine code not to die if it can't // load the JRI native DLLs. This allows // us to catch the UnsatisfiedLinkError // ourselves System.setProperty("jri.ignore.ule", "yes"); rosudaEngine = new JRIEngine(new String[] { "--no-save" }, rloopHandler); } } 

È ansible aggiungere un metodo di inizializzazione per separare l’istanza dall’ottenere.

 public class Singleton { private static Singleton singleton = null; private final int x; private Singleton(int x) { this.x = x; } public static Singleton getInstance() { if(singleton == null) { throw new AssertionError("You have to call init first"); } return singleton; } public synchronized static Singleton init(int x) { if (singleton != null) { // in my opinion this is optional, but for the purists it ensures // that you only ever get the same instance when you call getInstance throw new AssertionError("You already initialized me"); } singleton = new Singleton(x); return singleton; } } 

Quindi si chiama Singleton.init(123) una volta da qualche parte per configurarlo, ad esempio nell’avvio dell’app.

Il motivo per cui non riesci a capire come ottenere ciò che stai cercando di fare è probabilmente che ciò che stai cercando di fare non ha molto senso. Vuoi chiamare getInstance(x) con argomenti diversi, ma restituire sempre lo stesso object? Che comportamento vuoi quando chiami getInstance(2) e poi getInstance(5) ?

Se si desidera che lo stesso object, ma per il suo valore interno sia diverso, che è l’unico modo in cui è ancora un singleton, non è necessario preoccuparsi del costruttore; è sufficiente impostare il valore in getInstance() sulla via d’uscita dell’object. Ovviamente capisci che tutti gli altri riferimenti al singleton ora hanno un valore interno diverso.

Se vuoi getInstance(2) e getInstance(5) per restituire oggetti diversi, d’altra parte, non stai usando il pattern Singleton, stai usando il pattern Factory.

Nel tuo esempio non stai usando un singleton. Si noti che se si esegue la seguente operazione (presupponendo che Singleton.getInstance sia effettivamente statico):

 Singleton obj1 = Singleton.getInstance(3); Singleton obj2 = Singleton.getInstance(4); 

Quindi i valori di obj2.x sono 3, non 4. Se devi fare ciò, rendi una class semplice. Se il numero di valori è piccolo e fisso, puoi considerare l’utilizzo di un enum . Se hai problemi con la generazione eccessiva di oggetti (che di solito non è il caso), puoi prendere in considerazione i valori di memorizzazione nella cache (e controllare le fonti o ottenere aiuto, poiché è ovvio come build cache senza il rischio di perdite di memoria).

Si potrebbe anche voler leggere questo articolo come singleton può essere molto facilmente abusato.

Un altro motivo per cui Singletons è un anti-pattern è che, se scritti secondo le raccomandazioni, con un costruttore privato, sono molto difficili da sottoclass e da configurare per l’uso in alcuni test di unità. Sarebbe necessario per mantenere il codice legacy, ad esempio.

Se consideriamo il problema come “come rendere Singleton con stato”, non è necessario passare lo stato come parametro costruttore. Sono d’accordo con i post che inizializzano gli stati o utilizzando il metodo set dopo aver ottenuto l’istanza singleton.

Un’altra domanda è: è bello avere un singleton con stato?

Un singleton con parametri non è un singleton ” la frase non è completamente corretta . Dobbiamo analizzare questo dal punto di vista dell’applicazione piuttosto che dal punto di vista del codice.

Costruiamo una class singleton per creare una singola istanza di un object in un’esecuzione di un’applicazione. Avendo un costruttore con parametro, è ansible creare flessibilità nel codice per modificare alcuni attributi del proprio object singleton ogni volta che si esegue l’applicazione. Questa non è una violazione del modello di Singleton. Sembra una violazione se la vedi dal punto di vista del codice.

I modelli di progettazione sono lì per aiutarci a scrivere codice flessibile ed estensibile, non a impedirci di scrivere un buon codice.

Se si desidera creare una class Singleton che serve come contesto, un buon modo è quello di avere un file di configurazione e leggere i parametri dal file all’interno dell’istanza ().

Se i parametri che alimentano la class Singleton vengono visualizzati dynamicmente durante l’esecuzione del programma, è sufficiente utilizzare una HashMap statica che memorizza diverse istanze nella class Singleton per garantire che per ogni parametro venga creata una sola istanza.

Questo non è un singleton, ma potrebbe essere qualcosa che potrebbe risolvere il tuo problema.

 public class KamilManager { private static KamilManager sharedInstance; /** * This method cannot be called before calling KamilManager constructor or else * it will bomb out. * @return */ public static KamilManager getInstanceAfterInitialized() { if(sharedInstance == null) throw new RuntimeException("You must instantiate KamilManager once, before calling this method"); return sharedInstance; } public KamilManager(Context context, KamilConfig KamilConfig) { //Set whatever you need to set here then call: s haredInstance = this; } } 

Non potremmo fare qualcosa del genere:

 public class Singleton { private int x; // Private constructor prevents instantiation from other classs private Singleton() {} /** * SingletonHolder is loaded on the first execution of Singleton.getInstance() * or the first access to SingletonHolder.INSTANCE, not before. */ private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(int x) { Singleton instance = SingletonHolder.INSTANCE; instance.x = x; return instance; } } 

Nonostante ciò che alcuni possono affermare, ecco un singleton con parametri nel costruttore

 public class Singleton { private static String aParameterStored; private static final Singleton instance = new Singleton("Param to set"); private Singleton() { // do nothing } private Singleton(String param) { aParameterStored = param; } public static Singleton getInstance() { return instance; } /* * ... stuff you would like the singleton do */ } 

Il modello singleton dice:

  • assicurarsi che esista solo una istanza della class Singleton
  • fornire accesso globale a tale istanza.

che sono rispettati con questo esempio.

Perché non impostare direttamente la proprietà? È un caso da manuale per mostrare come possiamo ottenere un singleton con un costruttore con parametro ma potrebbe essere utile in alcune situazioni. Ad esempio nei casi di ereditarietà, per forzare il singleton ad impostare alcune proprietà della superclass.

Singleton è, naturalmente, un “anti-pattern” (assumendo una definizione di statica con stato variabile).

Se vuoi un insieme fisso di oggetti valore immutabili, allora le enumerazioni sono la strada da percorrere. Per un insieme di valori ampio, possibilmente di tipo aperto, è ansible utilizzare un repository di qualche forma, solitamente basato sull’implementazione di una Map . Ovviamente, quando si ha a che fare con la statica, sii cauto con il threading (o sincronizza sufficientemente la larghezza o usa un ConcurrentMap o verifichi che un altro thread non ti abbia battuto o usi qualche forma di futures).

Penso che questo sia un problema comune. Separare la “inizializzazione” del singleton dal “get” del singleton potrebbe funzionare (in questo esempio viene utilizzata una variante di double check locking).

 public class MySingleton { private static volatile MySingleton INSTANCE; @SuppressWarnings("UnusedAssignment") public static void initialize( final SomeDependency someDependency) { MySingleton result = INSTANCE; if (result != null) { throw new IllegalStateException("The singleton has already " + "been initialized."); } synchronized (MySingleton.class) { result = INSTANCE; if (result == null) { INSTANCE = result = new MySingleton(someDependency); } } } public static MySingleton get() { MySingleton result = INSTANCE; if (result == null) { throw new IllegalStateException("The singleton has not been " + "initialized. You must call initialize(...) before " + "calling get()"); } return result; } ... } 

Solitamente i single sono considerati anti-pattern e non dovrebbero essere usati. Non rendono il codice facile da testare.

Un singleton con argomento non ha comunque senso – cosa succederebbe se scrivessi:

 Singleton s = SingletonHolder.getInstance(1); Singleton t = SingletonHolder.getInstance(2); //should probably throw IllegalStateException 

Anche il singleton non è thread-safe in quanto più thread possono effettuare chiamate simultanee a getInstance determinando la creazione di più istanze (eventualmente con valori diversi di x ).