La variabile globale glorificata - diventa una classe globale gloriosa. Alcuni dicono che rompono il design orientato agli oggetti.
Dammi scenari, oltre al buon vecchio logger in cui ha senso usare il singleton.
La variabile globale glorificata - diventa una classe globale gloriosa. Alcuni dicono che rompono il design orientato agli oggetti.
Dammi scenari, oltre al buon vecchio logger in cui ha senso usare il singleton.
Risposte:
Nella mia ricerca della verità ho scoperto che in realtà ci sono pochissime ragioni "accettabili" per usare un Singleton.
Un motivo che tende a ripetersi all'internet è quello di una classe di "registrazione" (che hai menzionato). In questo caso, un Singleton può essere utilizzato al posto di una singola istanza di una classe perché una classe di registrazione di solito deve essere utilizzata più e più volte alla nausea da ogni classe in un progetto. Se ogni classe utilizza questa classe di registrazione, l'iniezione di dipendenza diventa ingombrante.
La registrazione è un esempio specifico di Singleton "accettabile" perché non influisce sull'esecuzione del codice. Disabilita la registrazione, l'esecuzione del codice rimane la stessa. Abilitalo, lo stesso. Misko lo inserisce nel modo seguente in Causa principale dei singoli , "Le informazioni qui fluiscono in un modo: dall'applicazione al logger. Anche se i logger sono stati globali, dal momento che nessuna informazione scorre dai logger all'applicazione, i logger sono accettabili".
Sono sicuro che ci sono anche altri validi motivi. Alex Miller, in " Patterns I Hate ", parla di localizzatori di servizi e di UI lato client che potrebbero anche essere scelte "accettabili".
Un candidato Singleton deve soddisfare tre requisiti:
Se il tuo Singleton proposto ha solo uno o due di questi requisiti, una riprogettazione è quasi sempre l'opzione corretta.
Ad esempio, è improbabile che uno spooler di stampa venga chiamato da più di una posizione (il menu Stampa), quindi è possibile utilizzare i mutex per risolvere il problema di accesso simultaneo.
Un semplice logger è l'esempio più ovvio di un Singleton possibilmente valido, ma questo può cambiare con schemi di registrazione più complessi.
Leggere i file di configurazione che devono essere letti solo al momento dell'avvio e incapsularli in un Singleton.
Properties.Settings.Default
.NET.
Si utilizza un singleton quando è necessario gestire una risorsa condivisa. Ad esempio uno spooler per stampante. L'applicazione deve avere un'unica istanza dello spooler per evitare richieste contrastanti per la stessa risorsa.
O una connessione al database o un file manager ecc.
Sono leggibili solo i singoli punti in cui sono memorizzati alcuni stati globali (lingua dell'utente, percorso file della guida, percorso dell'applicazione). Fai attenzione a usare i singleton per controllare la logica aziendale - il single finisce quasi sempre per essere multiplo
Gestire una connessione (o un pool di connessioni) a un database.
Lo userei anche per recuperare e archiviare informazioni su file di configurazione esterni.
Uno dei modi in cui usi un singleton è quello di coprire un'istanza in cui deve esserci un unico "broker" che controlla l'accesso a una risorsa. I singleton sono buoni nei logger perché mediano l'accesso, per esempio, a un file, che può essere scritto esclusivamente. Per qualcosa come la registrazione, forniscono un modo per sottrarre le scritture a qualcosa come un file di registro: potresti avvolgere un meccanismo di memorizzazione nella cache per il tuo singleton, ecc ...
Pensa anche a una situazione in cui hai un'applicazione con molte finestre / thread / etc, ma che ha bisogno di un singolo punto di comunicazione. Una volta ne ho usato uno per controllare i lavori che volevo avviare la mia applicazione. Il singleton era responsabile della serializzazione dei lavori e della visualizzazione del loro stato a qualsiasi altra parte del programma che fosse interessata. In questo tipo di scenario, puoi vedere un singleton come una sorta di classe "server" in esecuzione all'interno della tua applicazione ... HTH
Un singleton dovrebbe essere usato quando si gestisce l'accesso a una risorsa condivisa dall'intera applicazione e sarebbe distruttivo avere potenzialmente più istanze della stessa classe. Fare in modo che l'accesso al thread delle risorse condivise sia sicuro è un ottimo esempio di dove questo tipo di modello può essere vitale.
Quando si utilizzano i Singleton, è necessario assicurarsi di non nascondere accidentalmente dipendenze. Idealmente, i singoli (come la maggior parte delle variabili statiche in un'applicazione) devono essere impostati durante l'esecuzione del codice di inizializzazione per l'applicazione (static void Main () per eseguibili C #, static void main () per eseguibili java) e quindi passati a tutte le altre classi istanziate che lo richiedono. Questo ti aiuta a mantenere la testabilità.
Un esempio pratico di un singleton può essere trovato in Test :: Builder , la classe che supporta quasi ogni moderno modulo di test Perl. Test :: Builder singleton memorizza e negozia lo stato e la cronologia del processo di test (risultati dei test storici, conta il numero di test eseguiti) e cose come dove sta andando l'output del test. Questi sono tutti necessari per coordinare più moduli di test, scritti da autori diversi, per lavorare insieme in un unico script di test.
La storia del singleton di Test :: Builder è educativa. Chiamare new()
ti dà sempre lo stesso oggetto. Innanzitutto, tutti i dati sono stati memorizzati come variabili di classe senza nulla nell'oggetto stesso. Funzionò fino a quando volevo testare Test :: Builder con se stesso. Quindi avevo bisogno di due oggetti Test :: Builder, un setup come un manichino, per catturare e testare il suo comportamento e output, e uno per essere il vero oggetto di test. A quel punto Test :: Builder è stato trasformato in un oggetto reale. L'oggetto singleton era archiviato come dati di classe e new()
lo restituiva sempre. create()
è stato aggiunto per creare un nuovo oggetto e abilitare i test.
Attualmente, gli utenti vogliono cambiare alcuni comportamenti di Test :: Builder nel proprio modulo, ma lasciarne altri da soli, mentre la cronologia dei test rimane in comune su tutti i moduli di test. Ciò che sta accadendo ora è che l'oggetto monolitico Test :: Builder viene suddiviso in pezzi più piccoli (cronologia, output, formato ...) con un'istanza Test :: Builder che li raccoglie insieme. Ora Test :: Builder non deve più essere un singleton. I suoi componenti, come la storia, possono essere. Ciò spinge la necessità inflessibile di un singleton di un livello. Offre maggiore flessibilità all'utente nel mescolare e abbinare i pezzi. Gli oggetti singleton più piccoli ora possono semplicemente archiviare i dati, con i loro oggetti di contenimento che decidono come usarli. Permette persino a una classe non Test :: Builder di suonare insieme usando la storia Test :: Builder e i singleton di output.
Sembra esserci una spinta e una spinta tra coordinamento dei dati e flessibilità del comportamento che possono essere mitigate mettendo il singleton attorno solo ai dati condivisi con il minor numero di comportamenti possibile per garantire l'integrità dei dati.
Quando si carica un oggetto Proprietà di configurazione, dal database o da un file, è utile averlo come singleton; non c'è motivo di continuare a rileggere i dati statici che non cambieranno mentre il server è in esecuzione.
È possibile utilizzare Singleton quando si implementa il modello di stato (nel modo mostrato nel libro GoF). Questo perché le classi statali concrete non hanno uno stato proprio e svolgono le loro azioni in termini di classe contestuale.
Puoi anche rendere Abstract Factory un singleton.
setState()
responsabilità di decidere la politica di creazione dello stato. Aiuta se il tuo linguaggio di programmazione supporta modelli o generici. Invece di Singleton, è possibile utilizzare il modello Monostate , in cui l'istanza di un oggetto stato finisce per riutilizzare lo stesso oggetto stato globale / statico. La sintassi per cambiare lo stato potrebbe rimanere invariata, poiché gli utenti non devono essere consapevoli del fatto che lo stato istanziato è un Monostato.
Risorse condivise. Soprattutto in PHP, una classe di database, una classe modello e una classe di deposito variabile globale. Tutti devono essere condivisi da tutti i moduli / classi che vengono utilizzati in tutto il codice.
È un vero utilizzo dell'oggetto -> la classe template contiene il modello di pagina che viene creato e viene modellato, aggiunto, modificato dai moduli che si stanno aggiungendo all'output della pagina. Deve essere conservato come una singola istanza in modo che ciò accada, e lo stesso vale per i database. Con un singleton di database condiviso, tutte le classi dei moduli possono accedere alle query e ottenerle senza doverle rieseguire.
Un deposito di variabili globali singleton offre un deposito di variabili globale, affidabile e facilmente utilizzabile. Riordina molto il tuo codice. Immagina di avere tutti i valori di configurazione in un array in un singleton come:
$gb->config['hostname']
o con tutti i valori di lingua in un array come:
$gb->lang['ENTER_USER']
Alla fine dell'esecuzione del codice per la pagina, ottieni, diciamo, un ormai maturo:
$template
Singleton, un $gb
singleton che ha l'array lang da sostituire in esso e tutto l'output è caricato e pronto. Li sostituisci semplicemente nelle chiavi che sono ora presenti nel valore di pagina dell'oggetto modello maturo e poi lo distribuisci all'utente.
Il grande vantaggio di questo è che puoi fare QUALSIASI post-elaborazione che ti piace su qualsiasi cosa. Puoi reindirizzare tutti i valori della lingua a google translate o a un altro servizio di traduzione e ripristinarli e sostituirli nei loro luoghi, tradotti, ad esempio. oppure puoi sostituire le strutture di pagina o le stringhe di contenuto come desideri.
Può essere molto pragmatico configurare problemi specifici dell'infrastruttura come singoli o variabili globali. Il mio esempio preferito di ciò sono i framework di Iniezione delle dipendenze che fanno uso di singoli per fungere da punto di connessione al framework.
In questo caso si sta assumendo una dipendenza dall'infrastruttura per semplificare l'utilizzo della libreria ed evitare complessità non necessarie.
Lo uso per un oggetto che incapsula i parametri della riga di comando quando si tratta di moduli collegabili. Il programma principale non sa quali sono i parametri della riga di comando per i moduli che vengono caricati (e non sa nemmeno nemmeno quali moduli vengono caricati). ad esempio, i carichi principali A, che non hanno bisogno di alcun parametro (quindi perché dovrebbe richiedere un puntatore / riferimento extra / qualunque cosa, non sono sicuro - sembra inquinamento), quindi carica i moduli X, Y e Z. Due di questi, diciamo X e Z, necessitano (o accettano) dei parametri, quindi richiamano al singleton della riga di comando per dirgli quali parametri accettare, e in fase di esecuzione richiamano per scoprire se l'utente ha effettivamente specificato di loro.
In molti modi, un singleton per la gestione dei parametri CGI funzionerebbe allo stesso modo se stai usando un solo processo per query (altri metodi mod_ * non lo fanno, quindi sarebbe male lì - quindi l'argomento che dice che non dovresti ' usare singleton nel mondo mod_cgi nel caso in cui porti su mod_perl o in qualunque altro mondo).
Un esempio con il codice, forse.
Qui, il ConcreteRegistry è un singleton in un gioco di poker che consente ai comportamenti fino all'albero del pacchetto di accedere alle poche interfacce di base del gioco (ovvero le facciate per il modello, la vista, il controller, l'ambiente, ecc.):
http://www.edmundkirwan.com/servlet/fractal/cs1/frac-cs40.html
Ed.
1 - Un commento sulla prima risposta:
Non sono d'accordo con una classe Logger statica. questo può essere pratico per un'implementazione, ma non può essere sostituibile per test unitari. Una classe statica non può essere sostituita da un doppio di prova. Se non esegui il test unitario, non vedrai il problema qui.
2 - Cerco di non creare un singleton a mano. Creo semplicemente un oggetto semplice con costruttori che mi consentono di iniettare collaboratori nell'oggetto. Se avessi bisogno di un singleton, utilizzerei un framework di inection di dipendenza (Spring.NET, Unity per .NET, Spring per Java) o qualche altro.
ILogger logger = Logger.SingleInstance();
dove questo metodo è statico e restituisce un'istanza memorizzata staticamente di un ILogger. Hai usato l'esempio di "un framework di iniezione di dipendenza". Quasi tutti i contenitori DI sono singoli; le loro configurazioni sono definite staticamente e infine accessibili da / archiviate in un'unica interfaccia del fornitore di servizi.