Iniezione delle dipendenze e pattern di progettazione singleton


92

Come si identifica quando utilizzare l'inserimento delle dipendenze o il pattern singleton. Ho letto in molti siti Web in cui si dice "Use Dependency injection over singleton pattern". Ma non sono sicuro di essere totalmente d'accordo con loro. Per i miei progetti su piccola o media scala, vedo sicuramente l'uso del pattern singleton semplice.

Ad esempio Logger. Potrei usare Logger.GetInstance().Log(...) Ma, invece di questo, perché devo iniettare ogni classe che creo, con l'istanza del logger ?.

Risposte:


67

Se vuoi verificare cosa viene registrato in un test, hai bisogno di inserimento delle dipendenze. Inoltre, un logger è raramente un singleton: generalmente hai un logger per ciascuna classe.

Guarda questa presentazione sul design orientato agli oggetti per la testabilità e vedrai perché i singleton sono cattivi.

Il problema con i singleton è che rappresentano uno stato globale difficile da prevedere, soprattutto nei test.

Tieni presente che un oggetto può essere de facto singleton ma può comunque essere ottenuto tramite l'iniezione di dipendenze, piuttosto che tramite Singleton.getInstance().

Sto solo elencando alcuni punti importanti fatti da Misko Hevery nella sua presentazione. Dopo averlo visto, acquisirai una prospettiva completa sul motivo per cui è meglio che un oggetto definisca quali sono le sue dipendenze, ma non definisci un modo per crearle .


89

I single sono come il comunismo: entrambi suonano benissimo sulla carta, ma esplodono di problemi nella pratica.

Il pattern singleton pone un'enfasi sproporzionata sulla facilità di accesso agli oggetti. Evita completamente il contesto richiedendo che ogni consumatore utilizzi un oggetto con ambito AppDomain, senza lasciare opzioni per diverse implementazioni. Incorpora la conoscenza dell'infrastruttura nelle tue classi (la chiamata a GetInstance()) aggiungendo esattamente zero potenza espressiva. In realtà diminuisce il tuo potere espressivo, perché non puoi cambiare l'implementazione usata da una classe senza cambiarla per tutte . Semplicemente non puoi aggiungere funzionalità una tantum.

Inoltre, quando la classe Foodipende Logger.GetInstance(), Foonasconde efficacemente le sue dipendenze ai consumatori. Ciò significa che non puoi comprenderlo completamente Fooo usarlo con fiducia a meno che tu non legga la sua fonte e scopri il fatto che dipende Logger. Se non si dispone del codice sorgente, ciò limita la capacità di comprendere e utilizzare efficacemente il codice da cui si dipende.

Il modello singleton, implementato con proprietà / metodi statici, è poco più di un trucco per implementare un'infrastruttura. Ti limita in una miriade di modi, senza offrire alcun vantaggio distinguibile rispetto alle alternative. Puoi usarlo come preferisci, ma poiché ci sono alternative valide che promuovono un design migliore, non dovrebbe mai essere una pratica consigliata.


9
@BryanWatts tutto ciò che ha detto, i Singleton sono ancora molto più veloci e meno soggetti a errori in una normale applicazione su scala media. Di solito non ho più di una possibile implementazione per un logger (personalizzato) quindi perché il ** dovrei aver bisogno di: 1. Creare un'interfaccia per questo e cambiarla ogni volta che ho bisogno di aggiungere / cambiare membri pubblici 2. Mantenere una configurazione DI 3. Nascondere il fatto che c'è un singolo oggetto di questo tipo nell'intero sistema 4. Legami a una funzionalità rigorosa causata da una prematura Separation Of Concerns.
Uri Abramson

@UriAbramson: Sembra che tu abbia già preso la decisione sui compromessi che preferisci, quindi non cercherò di convincerti.
Bryan Watts

@BryanWatts Non è il caso, forse il mio commento è stato un po 'troppo duro, ma in realtà mi interessa molto sentire cosa hai da dire sul punto che ho sollevato ...
Uri Abramson

2
@UriAbramson: abbastanza giusto. Saresti almeno d'accordo sul fatto che lo scambio di implementazioni per l'isolamento durante il test è importante?
Bryan Watts

6
L'uso dei singleton e dell'inserimento delle dipendenze non si escludono a vicenda. Un singleton può implementare un'interfaccia, quindi può essere utilizzato per soddisfare una dipendenza da un'altra classe. Il fatto che sia un singleton non obbliga ogni consumatore a ottenere un riferimento tramite il suo metodo / proprietà "GetInstance".
Oliver

19

Altri hanno spiegato molto bene il problema con i singleton in generale. Vorrei solo aggiungere una nota sul caso specifico di Logger. Sono d'accordo con te sul fatto che di solito non è un problema accedere a un logger (o al root logger, per la precisione) come singleton, tramite uno statico getInstance()o un getRootLogger()metodo. (a meno che tu non voglia vedere cosa viene registrato dalla classe che stai testando - ma nella mia esperienza difficilmente riesco a ricordare casi in cui ciò fosse necessario. D'altra parte, per altri questa potrebbe essere una preoccupazione più urgente).

IMO di solito un logger singleton non è un problema, poiché non contiene alcuno stato rilevante per la classe che stai testando. Cioè, lo stato del logger (e le sue possibili modifiche) non hanno alcun effetto sullo stato della classe testata. Quindi non rende i tuoi test unitari più difficili.

L'alternativa sarebbe iniettare il logger tramite il costruttore, in (quasi) ogni singola classe nella tua app. Per la coerenza delle interfacce, dovrebbe essere iniettato anche se la classe in questione non registra nulla al momento - l'alternativa sarebbe che quando scopri a un certo punto che ora devi registrare qualcosa da questa classe, hai bisogno di un logger, quindi è necessario aggiungere un parametro costruttore per DI, interrompendo tutto il codice client. Non mi piacciono entrambe queste opzioni e ritengo che usare DI per la registrazione complicherebbe la mia vita solo per rispettare una regola teorica, senza alcun beneficio concreto.

Quindi la mia conclusione è: una classe che viene utilizzata (quasi) universalmente, ma non contiene uno stato rilevante per la tua app, può essere tranquillamente implementata come Singleton .


1
Anche allora, un singleton può essere un dolore. Se ti rendi conto che il tuo logger ha bisogno di qualche parametro aggiuntivo per alcuni casi, puoi creare un nuovo metodo (e lasciare che il logger diventi più brutto) o distruggere tutti i consumatori del logger, anche se a loro non importa.
kyoryu

4
@kyoryu, sto parlando del "solito" caso, che IMHO implica l'utilizzo di un framework di registrazione standard (de facto). (Che in genere sono configurabili tramite proprietà / file XML tra l'altro.) Ovviamente ci sono delle eccezioni, come sempre. Se so che la mia app è eccezionale sotto questo aspetto, non userò davvero Singleton. Ma l'eccessiva ingegnerizzazione che dice "questo potrebbe essere utile prima o poi" è quasi sempre uno sforzo inutile.
Péter Török

Se stai già usando DI, non è molto ingegneristico extra. (BTW, non sono in disaccordo con te, sono il voto positivo). Molti logger richiedono alcune informazioni di "categoria" o qualcosa del genere e l'aggiunta di parametri aggiuntivi può causare problemi. Nasconderlo dietro un'interfaccia può aiutare a mantenere pulito il codice che consuma e può facilitare il passaggio a un diverso framework di registrazione.
kyoryu

1
@kyoryu, scusa per l'ipotesi sbagliata. Ho visto un voto negativo e un commento, quindi ho collegato i punti - nel modo sbagliato, a quanto pare :-( Non ho mai sentito la necessità di passare a un framework di registrazione diverso, ma poi di nuovo, capisco che potrebbe essere un legittimo preoccupazione in alcuni progetti
Péter Török

5
Correggimi se sbaglio, ma il singleton sarebbe per LogFactory, non per il logger. Inoltre, è probabile che LogFactory sia la facciata di log di Apache Common o Slf4j. Quindi cambiare le implementazioni di registrazione è comunque indolore. Non è il vero problema con l'utilizzo di DI per iniettare un LogFactory il fatto che devi andare su applicationContext ora per creare ogni istanza nella tua app?
HDave

9

Riguarda principalmente, ma non interamente, i test. I single erano popolari perché era facile consumarli, ma ci sono una serie di svantaggi nei singleton.

  • Difficile da testare. Significa come posso assicurarmi che il logger faccia la cosa giusta.
  • Difficile da testare. Significa che se sto testando un codice che utilizza il logger, ma non è l'obiettivo del mio test, devo comunque assicurarmi che il mio ambiente di test supporti il ​​logger
  • A volte non vuoi un singolo, ma più flessibilità

DI ti dà il facile utilizzo delle tue classi dipendenti - basta inserirlo negli argomenti del costruttore e il sistema te lo fornisce - mentre ti offre la flessibilità di test e costruzione.


Non proprio. Riguarda lo stato mutevole condiviso e le dipendenze statiche che rendono le cose dolorose a lungo termine. Il test è solo l'esempio ovvio e spesso il più doloroso.
kyoryu

2

L'unica volta in cui dovresti usare un Singleton invece di Dependency Injection è se Singleton rappresenta un valore immutabile, come List.Empty o simili (assumendo elenchi immutabili).

Il controllo dell'intestino per un singleton dovrebbe essere "starei bene se questa fosse una variabile globale invece di un singleton?" In caso contrario, stai utilizzando il modello Singleton per carta su una variabile globale e dovresti considerare un approccio diverso.


1

Ho appena controllato l'articolo di Monostate: è un'alternativa elegante a Singleton, ma ha alcune proprietà strane:

class Mono{
    public static $db;
    public function setDb($db){
       self::$db = $db;
    }

}

class Mapper extends Mono{
    //mapping procedure
    return $Entity;

    public function save($Entity);//requires database connection to be set
}

class Entity{
public function save(){
    $Mapper = new Mapper();
    $Mapper->save($this);//has same static reference to database class     
}

$Mapper = new Mapper();
$Mapper->setDb($db);

$User = $Mapper->find(1);
$User->save();

Non è questo tipo di paura - perché il Mapper dipende davvero dalla connessione al database per eseguire save () - ma se un altro mapper è stato creato in precedenza - può saltare questo passaggio per acquisire le sue dipendenze. Sebbene sia pulito, è anche un po 'disordinato, no?


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.