Qual è la differenza tra i modelli di Iniezione delle dipendenze e Localizzatore di servizi?


304

Entrambi i modelli sembrano un'implementazione del principio di inversione del controllo. Cioè, un oggetto non dovrebbe sapere come costruirne le dipendenze.

Dependency Injection (DI) sembra usare un costruttore o setter per "iniettare" le sue dipendenze.

Esempio di utilizzo dell'iniezione costruttore:

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo(IBar bar)
  {
    this.bar = bar;
  }

  //...
}

Service Locator sembra usare un "contenitore", che collega le sue dipendenze e dà alla sua barra.

Esempio di utilizzo di un Service Locator:

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo()
  {
    this.bar = Container.Get<IBar>();
  }

  //...
}

Poiché le nostre dipendenze sono solo oggetti stessi, queste dipendenze hanno dipendenze, che hanno ancora più dipendenze e così via e così via. Nacque così l'Inversion of Control Container (o DI Container). Esempi: Castle Windsor, Ninject, Map Structure, Spring, ecc.)

Ma un contenitore IOC / DI si presenta esattamente come un localizzatore di servizi. Chiamarlo contenitore DI è un brutto nome? Un container IOC / DI è solo un altro tipo di Service Locator? La sfumatura sta nel fatto che usiamo DI Container principalmente quando abbiamo molte dipendenze?


13
L'inversione del controllo significa che "un oggetto non dovrebbe sapere come costruirne le dipendenze"?!? Quello è nuovo per me. No, davvero, non è questo che significa "inversione di controllo". Vedi martinfowler.com/bliki/InversionOfControl.html Questo articolo fornisce persino riferimenti per l'etimologia del termine, risalente agli anni '80.
Rogério,


1
Mark Seemann sostiene Service Locator come anti-pattern ( blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern ). Inoltre, ho trovato il diagramma (trovato qui, stackoverflow.com/a/9503612/1977871 ) utile per comprendere la situazione DI e SL. Spero che questo ti aiuti.
VivekDev l'

Risposte:


181

La differenza può sembrare lieve, ma anche con ServiceLocator, la classe è ancora responsabile della creazione delle sue dipendenze. Usa semplicemente il localizzatore di servizi per farlo. Con DI, alla classe vengono date le sue dipendenze. Non sa né importa da dove vengono. Un risultato importante di questo è che l'esempio DI è molto più facile da testare unità, perché puoi passargli finte implementazioni dei suoi oggetti dipendenti. Potresti combinare i due - e iniettare il localizzatore di servizio (o una fabbrica), se lo desideri.


20
Inoltre, puoi usare entrambi durante la costruzione di una classe. Il costruttore predefinito può utilizzare SL per recuperare le dipendenze e passarle al costruttore "reale" che riceve tali dipendenze. Ottieni il meglio da entrambi i mondi.
Grant Palin,

6
No, ServiceLocator è il responsabile dell'istanza dell'implementazione corretta per una determinata dipendenza (plugin). Nel caso di DI, il "contenitore" DI è il responsabile.
Rogério,

5
@Rogerio sì, ma la classe deve ancora conoscere il Service Locator ... ecco due dipendenze. Inoltre, il più delle volte ho visto Service Locator delegare al contenitore DI per la ricerca, in particolare per gli oggetti transitori che necessitano del supporto del servizio.
Adam Gent,

2
@Adam Non ho detto che Service Locator avrebbe delegato a un contenitore DI. Questi sono due modelli reciprocamente esclusivi, come descritto nell'articolo "ufficiale" . Per me, Service Locator ha un enorme vantaggio rispetto a DI in pratica: l'uso di un contenitore DI invita ad abusi (che ho visto più volte), mentre l'uso di un Service Locator no.
Rogério,

3
"Un risultato importante di questo è che l'esempio DI è molto più facile da testare, perché puoi passargli finte implementazioni dei suoi oggetti dipendenti." Non vero. Nei test unitari, è possibile utilizzare una chiamata a una funzione di registro nel contenitore del localizzatore di servizio per aggiungere facilmente simulazioni al registro.
Drumbeg,

93

Quando si utilizza un localizzatore di servizi, ogni classe avrà una dipendenza dal localizzatore di servizi. Questo non è il caso dell'iniezione di dipendenza. L'iniettore di dipendenza viene in genere chiamato solo una volta all'avvio per iniettare dipendenze in alcune classi principali. Alle classi da cui dipende la classe principale verranno ricorsivamente iniettate le loro dipendenze, fino a quando non si avrà un grafico a oggetti completo.

Un buon confronto: http://martinfowler.com/articles/injection.html

Se il tuo iniettore di dipendenza sembra un localizzatore di servizi, dove le classi chiamano direttamente l'iniettore, probabilmente non è un iniettore di dipendenze, ma piuttosto un localizzatore di servizi.


17
Ma come gestisci il caso in cui devi creare oggetti durante il runtime? Se li crei manualmente con "nuovo" non puoi utilizzare DI. Se si chiama il framework DI per aiuto, si interrompe il modello. Quindi quali opzioni sono rimaste?
Boris,

9
@Boris Ho avuto lo stesso problema e ho deciso di iniettare fabbriche specifiche per classe. Non carino ma fatto il lavoro. Mi piacerebbe vedere una soluzione più bella.
Charlie Rudenstål,

Link diretto al confronto: martinfowler.com/articles/…
Teoman shipahi,

2
@Boris Se avessi bisogno di costruire nuovi oggetti al volo, inietterei una Fabbrica astratta per detti oggetti. Il che sarebbe simile all'iniezione di un localizzatore di servizi in questo caso, ma fornisce un'interfaccia concreta, uniforme, in fase di compilazione, per la costruzione degli oggetti rilevanti e rende esplicite le dipendenze.
LivePastTheEnd

51

I localizzatori di servizi nascondono le dipendenze: non si capisce guardando un oggetto se colpisce o meno un database (ad esempio) quando ottiene connessioni da un localizzatore. Con l'iniezione delle dipendenze (almeno l'iniezione del costruttore) le dipendenze sono esplicite.

Inoltre, i localizzatori di servizi interrompono l'incapsulamento perché forniscono un punto di accesso globale alle dipendenze di altri oggetti. Con localizzatore di servizi, come con qualsiasi singolo :

diventa difficile specificare le condizioni pre e post per l'interfaccia dell'oggetto client, poiché i meccanismi della sua implementazione possono essere intromessi dall'esterno.

Con l'iniezione delle dipendenze, una volta specificate le dipendenze di un oggetto, queste sono sotto il controllo dell'oggetto stesso.


3
Preferisco "Singletons Considered Stupid", steve.yegge.googlepages.com/singleton-considered-stupid
Charles Graham,

2
Adoro il vecchio Steve Yegge e il titolo di quell'articolo è fantastico, ma penso che l'articolo che ho citato e i "Singletons sono bugiardi patologici" di Miško Hevery ( misko.hevery.com/2008/08/17/singletons-are-pathological- bugiardi ) fanno un caso migliore contro la particolare diavoleria del localizzatore di servizi.
Jeff Sternal,

Questa risposta è la più corretta perché definisce meglio un localizzatore di servizi: "Una classe che nasconde le sue dipendenze". Si noti che la creazione interna di una dipendenza, sebbene spesso non sia una buona cosa, non rende una classe un localizzatore di servizi. Inoltre, prendere una dipendenza da un container è un problema ma non "il" problema che definisce più chiaramente un localizzatore di servizi.
Sam,

1
With dependency injection (at least constructor injection) the dependencies are explicit.. Spiega per favore.
FindOutIslamNow,

Come sopra, non riesco a vedere come SL renda le dipendenze meno esplicite di DI ...
Michał Powłoka,

38

Martin Fowler afferma :

Con il localizzatore di servizi, la classe dell'applicazione lo richiede esplicitamente tramite un messaggio al localizzatore. Con l'iniezione non c'è una richiesta esplicita, il servizio appare nella classe dell'applicazione - da qui l'inversione del controllo.

In breve: Service Locator e Dependency Injection sono solo implementazioni del principio di inversione di dipendenza.

Il principio importante è "Dipendere dalle astrazioni, non dalle concrezioni". Ciò renderà il vostro progetto software “liberamente accoppiato”, “estensibile”, “flessibile”.

Puoi usare quello più adatto alle tue esigenze. Per un'applicazione di grandi dimensioni, con una base di codice enorme, è meglio utilizzare un localizzatore di servizi, poiché l'iniezione delle dipendenze richiederebbe ulteriori modifiche alla base di codice.

È possibile controllare questo post: Inversione dipendenze: Localizzatore servizi o Iniezione dipendenze

Anche il classico: Inversion of Control Containers e il modello Dependency Injection di Martin Fowler

Progettazione di classi riutilizzabili di Ralph E. Johnson e Brian Foote

Tuttavia, quello che mi ha aperto gli occhi è stato: ASP.NET MVC: Risolvi o Inietta? Questo è il problema ... di Dino Esposito


Riepilogo fantastico: "Localizzatore di servizi e Iniezione delle dipendenze sono solo implementazioni del principio di inversione delle dipendenze".
Hans,

E afferma anche: La differenza chiave è che con un Service Locator ogni utente di un servizio ha una dipendenza dal localizzatore. Il localizzatore può nascondere dipendenze da altre implementazioni, ma è necessario vedere il localizzatore. Quindi la decisione tra localizzatore e iniettore dipende dal fatto che tale dipendenza sia un problema.
Programmi

1
ServiceLocator e DI non hanno nulla a che fare con il "Principio di inversione di dipendenza" (DIP). DIP è un modo per rendere più riutilizzabile un componente di alto livello, sostituendo una dipendenza in fase di compilazione su un componente di basso livello con una dipendenza da un tipo astratto definito insieme al componente di alto livello, che viene implementato dal basso componente di livello; in questo modo, la dipendenza del tempo di compilazione viene invertita, poiché ora è quella di basso livello che dipende da quella di alto livello. Inoltre, nota che l'articolo di Martin Fowler spiega che DI e IoC non sono la stessa cosa.
Rogério,

23

Una classe che utilizza il costruttore DI indica al codice che consuma che ci sono dipendenze da soddisfare. Se la classe utilizza la SL internamente per recuperare tali dipendenze, il codice di consumo non è a conoscenza delle dipendenze. In superficie ciò può sembrare migliore, ma in realtà è utile conoscere eventuali dipendenze esplicite. È meglio da un punto di vista architettonico. E quando si eseguono i test, è necessario sapere se una classe necessita di determinate dipendenze e configurare SL per fornire le versioni false appropriate di tali dipendenze. Con DI, passa i falsi. Non è una grande differenza, ma è lì.

DI e SL possono lavorare insieme, però. È utile avere una posizione centrale per dipendenze comuni (ad es. Impostazioni, logger, ecc.). Data una classe che utilizza tali deps, è possibile creare un costruttore "reale" che riceve i deps e un costruttore predefinito (nessun parametro) che recupera da SL e inoltra al costruttore "reale".

EDIT: e, naturalmente, quando usi SL, stai introducendo un accoppiamento a quel componente. Il che è ironico, poiché l'idea di tale funzionalità è di incoraggiare le astrazioni e ridurre l'accoppiamento. Le preoccupazioni possono essere bilanciate e dipende dal numero di posti necessari per utilizzare SL. Se fatto come suggerito sopra, proprio nel costruttore di classi predefinito.


Interessante! Sto usando sia DI che SL, ma non con due costruttori. Tre o quattro dipendenze spesso noiose (impostazioni, ecc ...) vengono ottenute al volo da SL. Tutto il resto viene iniettato dal costruttore. È un po 'brutto, ma pragmatico.
maaartinus,

10

Entrambi sono tecniche di implementazione dell'IoC. Esistono anche altri schemi che implementano Inversion of Control:

  • Modello di fabbrica
  • Localizzatore di servizi
  • Contenitore DI (IoC)
  • Iniezione delle dipendenze (iniezione del costruttore, iniezione dei parametri (se non richiesta), iniezione del setter dell'iniezione dell'interfaccia) ...

Service locator e DI Container sembrano più simili, entrambi usano un container per definire le dipendenze, che mappa l'astrazione all'implementazione concreta.

La differenza principale è come si trovano le dipendenze, nel Service Locator, il codice client richiede le dipendenze, in DI Container usiamo un contenitore per creare tutti gli oggetti e inietta dipendenza come parametri (o proprietà) del costruttore.


7

Nel mio ultimo progetto uso entrambi. Uso l'iniezione di dipendenza per testabilità dell'unità. Uso il localizzatore di servizi per nascondere l'implementazione e dipendere dal mio contenitore IoC. e sì! Una volta che usi uno dei container IoC (Unity, Ninject, Windsor Castle) dipendi da esso. E una volta che è obsoleto o per qualche motivo se si desidera scambiarlo, sarà / potrebbe essere necessario modificare l'implementazione - almeno la radice della composizione. Ma il localizzatore di servizio estrae quella fase.

Come non dipenderesti dal tuo contenitore IoC? O dovrai avvolgerlo da solo (che è una cattiva idea) o utilizzare Service Locator per configurare il tuo contenitore IoC. Quindi dirai a Service Locator di ottenere l'interfaccia che ti serve e chiamerà il contenitore IoC configurato per recuperare quell'interfaccia.

Nel mio caso, utilizzo ServiceLocator che è un componente di framework. E usa Unity per il contenitore IoC. Se in futuro dovrò scambiare il mio contenitore IoC su Ninject tutto ciò che devo fare è configurare il mio localizzatore di servizi per usare Ninject invece di Unity. Migrazione facile.

Ecco un ottimo articolo che spiega questo scenario; http://www.johandekoning.nl/index.php/2013/03/03/dont-wrap-your-ioc-container/


Il collegamento all'articolo johandekoning è interrotto.
JakeJ,

6

Penso che i due lavorino insieme.

Iniezione di dipendenza significa che si inserisce una classe / interfaccia dipendente in una classe consumante (di solito al suo costruttore). Questo disaccoppia le due classi tramite un'interfaccia e significa che la classe che consuma può lavorare con molti tipi di implementazioni di "dipendenza iniettata".

Il ruolo del localizzatore di servizi consiste nel mettere insieme la tua implementazione. All'inizio del programma si imposta un localizzatore di servizi tramite una cinghia di avvio. Il bootstrap è il processo di associazione di un tipo di implementazione a un particolare abstract / interfaccia. Che viene creato per te in fase di esecuzione. (basato sulla tua configurazione o bootstrap). Se non è stata implementata l'iniezione di dipendenza, sarebbe molto difficile utilizzare un localizzatore di servizio o un contenitore IOC.


6

Un motivo da aggiungere, ispirato a un aggiornamento della documentazione che abbiamo scritto per il progetto MEF la scorsa settimana (aiuto a costruire MEF).

Una volta che un'app è composta da potenzialmente migliaia di componenti, può essere difficile determinare se un particolare componente può essere istanziato correttamente. Per "istanziata correttamente", intendo che in questo esempio in base al Foocomponente, un'istanza di IBare sarà disponibile e che il componente che lo fornisce dovrà:

  • avere le dipendenze richieste,
  • non essere coinvolto in alcun ciclo di dipendenza non valido e
  • nel caso di MEF, essere fornito con una sola istanza.

Nel secondo esempio che hai fornito, in cui il costruttore va nel contenitore IoC per recuperare le sue dipendenze, l'unico modo in cui puoi verificare che un'istanza di Foosarà in grado di essere istanziato correttamente con l'effettiva configurazione di runtime della tua app è effettivamente costruire esso .

Questo ha tutti i tipi di effetti collaterali imbarazzanti al momento del test, perché il codice che funzionerà in fase di esecuzione non necessariamente funzionerà sotto un cablaggio di test. Le simulazioni non lo faranno, perché la vera configurazione è la cosa che dobbiamo testare, non una configurazione del tempo di test.

La radice di questo problema è la differenza già evidenziata da @Jon: l'iniezione di dipendenze attraverso il costruttore è dichiarativa, mentre la seconda versione utilizza il modello imperativo di Service Locator.

Un contenitore IoC, se usato con attenzione, può analizzare staticamente la configurazione di runtime della tua app senza effettivamente creare alcuna istanza dei componenti coinvolti. Molti contenitori popolari offrono alcune varianti di questo; Microsoft.Composition , che è la versione di MEF destinata alle app Web 4.5 e in stile Metro .NET, fornisce un CompositionAssertesempio nella documentazione wiki. Usandolo, puoi scrivere codice come:

 // Whatever you use at runtime to configure the container
var container = CreateContainer();

CompositionAssert.CanExportSingle<Foo>(container);

(Vedi questo esempio ).

Verificando le root di composizione della tua applicazione al momento del test, puoi potenzialmente rilevare alcuni errori che potrebbero altrimenti passare attraverso i test più avanti nel processo.

Spero che questa sia un'aggiunta interessante a questa serie altrimenti completa di risposte sull'argomento!


5

Nota: non sto rispondendo esattamente alla domanda. Ma ritengo che ciò possa essere utile per i nuovi apprendenti del modello di Iniezione delle dipendenze che sono confusi al riguardo con il modello (anti-) Service Locator che si imbatte in questa pagina.

Conosco la differenza tra Service Locator (ora sembra essere considerato un anti-pattern) e modelli di Iniezione delle dipendenze e posso capire esempi concreti di ogni modello, ma sono rimasto confuso da esempi che mostrano un localizzatore di servizi all'interno del costruttore (supponiamo che ' facendo l'iniezione del costruttore).

"Service Locator" è spesso usato sia come nome di un modello, sia come nome per riferirsi all'oggetto (si supponga anche) usato in quel modello per ottenere oggetti senza usare il nuovo operatore. Ora, lo stesso tipo di oggetto può essere utilizzato anche nella radice della composizione per eseguire l'iniezione di dipendenza, ed è qui che entra in gioco la confusione.

Il punto è che è possibile che si stia utilizzando un oggetto localizzatore di servizio all'interno di un costruttore DI, ma non si sta utilizzando il "modello di localizzatore di servizio". È meno confuso se uno lo chiama invece come un oggetto contenitore IoC, poiché potresti aver intuito che essenzialmente fanno la stessa cosa (correggimi se sbaglio).

Indipendentemente dal fatto che sia indicato come un localizzatore di servizi (o solo un localizzatore) o come un contenitore IoC (o solo contenitore) non fa alcuna differenza, come si suppone, poiché probabilmente si riferiscono alla stessa astrazione (correggimi se sbaglio ). È solo che chiamarlo un localizzatore di servizi suggerisce che si sta usando l'anti-schema Localizzatore di servizio insieme al modello di iniezione di dipendenza.

IMHO, nominandolo "localizzatore" anziché "posizione" o "localizzazione", può anche far pensare che il localizzatore di servizio in un articolo si riferisca al contenitore di Service Locator e non al modello di Service Locator (anti-) , specialmente quando esiste un modello correlato chiamato Iniezione delle dipendenze e non Iniettore delle dipendenze.


4

In questo caso semplificato non c'è differenza e possono essere usati in modo intercambiabile. Tuttavia, i problemi del mondo reale non sono così semplici. Supponi solo che la stessa classe Bar avesse un'altra dipendenza chiamata D. In quel caso il tuo localizzatore di servizi non sarebbe in grado di risolvere quella dipendenza e dovresti istanziarla all'interno della classe D; perché è responsabilità delle tue classi istanziare le loro dipendenze. Sarebbe anche peggio se la classe D stessa avesse altre dipendenze e nelle situazioni del mondo reale di solito diventa ancora più complicata di così. In tali scenari DI è una soluzione migliore di ServiceLocator.


4
Hmm non sarei d'accordo: il localizzatore di servizi ex. mostra chiaramente che c'è ancora una dipendenza lì ... il localizzatore di servizi. Se la barclasse stessa ha una dipendenza, allora baravrà anche un localizzatore di servizi, questo è il punto centrale dell'uso di DI / IoC.
GFoley83,

2

Qual è la differenza (se presente) tra Iniezione delle dipendenze e Localizzatore servizi? Entrambi i modelli sono efficaci nell'attuare il principio di inversione di dipendenza. Il modello di Service Locator è più facile da usare in una base di codice esistente in quanto rende il design generale più libero senza forzare le modifiche all'interfaccia pubblica. Per lo stesso motivo, il codice basato sul modello di Service Locator è meno leggibile del codice equivalente basato sull'iniezione delle dipendenze.

Il modello di Iniezione delle dipendenze chiarisce poiché la firma che dipenderà da una classe (o da un metodo). Per questo motivo, il codice risultante è più pulito e più leggibile.


1

Seguire una semplice concezione mi ha dato una comprensione più chiara della differenza tra Service Locator e DI Container:

  • Service Locator viene utilizzato nel consumatore e estrae i servizi tramite ID da alcuni archivi su richiesta del consumatore diretto

  • DI Container si trova da qualche parte all'esterno e prende servizi da un po 'di spazio di archiviazione e li spinge verso il consumatore (indipendentemente dal costruttore o dal metodo)

Tuttavia, possiamo parlare della differenza tra questi solo nel contesto dell'uso concreto del consumatore. Quando Service Locator e DI Container vengono utilizzati nella composizione root, sono quasi simili.


0

DI container è un superset of service locator. Può essere utilizzato per individuare un servizio , con ulteriore capacità di assemblare (cablare) le iniezioni di dipendenza .


-2

Per il record

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo(IBar bar)
  {
    this.bar = bar;
  }

  //...
}

A meno che tu non abbia davvero bisogno di un'interfaccia (l'interfaccia è utilizzata da più di una classe), NON DEVI USARLO . In questo caso, IBar consente di utilizzare qualsiasi classe di servizio, che la implementa. Tuttavia, di solito, questa interfaccia verrà utilizzata da una singola classe.

Perché è una cattiva idea usare un'interfaccia ?. Perché è davvero difficile eseguire il debug.

Ad esempio, supponiamo che l'istanza "bar" non sia riuscita, domanda: quale classe non è riuscita ?. Quale codice dovrei correggere? Una vista semplice, porta a un'interfaccia ed è qui che finisce la mia strada.

Invece, se il codice utilizza una dipendenza forte, è facile eseguire il debug di un errore.

//Foo Needs an IBar
public class Foo
{
  private BarService bar;

  public Foo(IBar bar)
  {
    this.bar = bar;
  }

  //...
}

Se "bar" fallisce, allora dovrei controllare e attivare la classe BarService.


1
Una classe è un modello per costruire un oggetto specifico. D'altra parte un'interfaccia è una contracte definisce solo un comportamento non l'azione. Invece di passare attorno all'oggetto reale, solo l'interfaccia è condivisa in modo che il consumatore non acceda al resto dell'oggetto. Anche per i test unitari aiuta a testare solo la parte che deve essere testata. Immagino che col tempo capiresti la sua utilità.
Gunhan,
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.