Abbiamo una classe che contiene informazioni di configurazione per l'applicazione. Era un singleton. Dopo alcune revisioni architettoniche, ci è stato detto di rimuovere il singleton. Abbiamo riscontrato alcuni vantaggi di non utilizzare singleton nel test unitario perché possiamo testare diverse configurazioni contemporaneamente.
Senza singleton, dobbiamo passare l'istanza ovunque nel nostro codice. Sta diventando così disordinato, quindi abbiamo scritto un wrapper singleton. Ora stiamo trasferendo lo stesso codice su PHP e .NET, mi chiedo se esiste un modello migliore che possiamo usare per l'oggetto di configurazione.
Google Testing blog contiene una serie di voci su come evitare Singleton (al fine di creare un codice testabile). Forse questo può aiutarti:
L'ultimo articolo spiega in dettaglio come spostare la creazione di nuovi oggetti in una fabbrica, in modo da poter evitare l'uso di singoli. Vale la pena leggere di sicuro.
In breve, trasferiamo tutti i nuovi operatori in una fabbrica. Raggruppiamo tutti gli oggetti di durata simile in un'unica fabbrica.
Il modo migliore è invece utilizzare un modello Factory. Quando costruisci una nuova istanza della tua classe (in fabbrica) puoi inserire i dati "globali" nell'oggetto appena costruito, sia come riferimento a una singola istanza (che memorizzi nella classe di fabbrica) o copiando la relativa dati nel nuovo oggetto.
Tutti i tuoi oggetti conterranno quindi i dati che vivevano nel singleton. Non credo che ci sia molta differenza nel complesso, ma può rendere più facile la lettura del codice.
Potrei affermare l'ovvio qui, ma c'è un motivo per cui non è possibile utilizzare un framework di iniezione di dipendenza come Spring o Guice ? (Credo che anche Spring sia disponibile anche per .NET).
In questo modo, il framework può contenere una singola copia degli oggetti di configurazione e i tuoi bean (servizi, DAO, qualunque cosa) non devono preoccuparsi di cercarli.
Questo è l'approccio che di solito prendo!
non accumulare responsabilità verso un singolo oggetto di configurazione poiché finirà in un oggetto molto grande che è sia difficile da capire che fragile.
Ad esempio, se hai bisogno di un altro parametro per una particolare classe, cambi l'oggetto Configuration
, quindi ricompila tutte le classi che lo usano. Questo è alquanto problematico.
Prova a refactoring del codice per evitare un oggetto Configuration
comune, globale e grande. Passare solo i parametri richiesti alle classi client:
class Server {
int port;
Server(Configuration config) {
this.port = config.getServerPort();
}
}
dovrebbe essere refactored per:
class Server {
public Server(int port) {
this.port = port;
}
}
a framework di iniezione delle dipendenze aiuterà molto qui, ma non è strettamente necessario.
Se usi Spring Framework , puoi semplicemente creare un bean normale. Per impostazione predefinita (o se si imposta esplicitamente scope="singleton"
) Viene creata solo un'istanza del bean e tale istanza viene restituita ogni volta che il bean viene utilizzato in una dipendenza o recuperato tramite getBean()
.
Ottieni il vantaggio della singola istanza, senza l'accoppiamento del modello Singleton.
L'alternativa è passare ciò di cui hai bisogno invece di chiedere un oggetto per le cose.
È possibile ottenere lo stesso comportamento di singleton utilizzando metodi statici. Steve yegge lo spiega molto bene in questo post.
Rivedere la possibilità di effettuare la configurazione come interfaccia di callback. Quindi il tuo codice sensibile alla configurazione apparirà:
MyReuseCode.Configure(IConfiguration)
Il codice di inizializzazione del sistema apparirà:
Library.init(MyIConfigurationImpl)
È possibile utilizzare un framework di iniezione delle dipendenze per alleviare il dolore del passaggio nell'oggetto di configurazione. Uno decente è ninject che ha il vantaggio di usare il codice piuttosto che xml.
È possibile una classe che contiene solo metodi e campi statici? Non sono sicuro di quale sia esattamente la tua situazione, ma potrebbe valere la pena esaminarlo.
Forse non è neanche molto pulito, ma potresti passare i bit di informazione che desideri siano cambiati nel metodo che crea il singleton - invece di usare
public static Singleton getInstance() {
if(singleton != null)
createSingleton();
return singleton;
}
}
è possibile chiamare createSingleton(Information info)
direttamente all'avvio dell'applicazione (e nei metodi setUp dei test unitari).
Dipende da quali strumenti/framework ecc. Vengono utilizzati. Con gli strumenti di iniezione delle dipendenze/ioc è spesso possibile ottenere prestazioni/ottimizzazioni singleton facendo in modo che il contenitore di/ioc utilizzi il comportamento singleton per la classe richiesta - (come un'interfaccia IConfigSettings) creando sempre e solo un'istanza della classe. Questo potrebbe essere ancora sostituito per il test
In alternativa, è possibile utilizzare una factory per creare la classe e restituire la stessa istanza ogni volta che viene richiesta, ma per il test potrebbe restituire una versione stub/beffata