¿Cuál es la diferencia entre los patrones de Inyección de dependencias y Localizador de servicios?

304

Ambos patrones parecen una implementación del principio de inversión de control. Es decir, que un objeto no debe saber cómo construir sus dependencias.

La inyección de dependencias (DI) parece usar un constructor o setter para "inyectar" sus dependencias.

Ejemplo de uso de inyección de constructor:

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

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

  //...
}

Service Locator parece usar un "contenedor", que conecta sus dependencias y le da a su barra.

Ejemplo de uso de un localizador de servicios:

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

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

  //...
}

Debido a que nuestras dependencias son solo objetos en sí mismas, estas dependencias tienen dependencias, que tienen incluso más dependencias, y así sucesivamente. Así nació la Inversión de Control Container (o DI Container). Ejemplos: Castillo Windsor, Ninject, Mapa de Estructura, Primavera, etc.)

Pero un contenedor IOC / DI se ve exactamente como un localizador de servicios. ¿Es un mal nombre llamarlo contenedor DI? ¿Es un contenedor IOC / DI simplemente otro tipo de localizador de servicios? ¿Es el matiz el hecho de que usamos contenedores DI principalmente cuando tenemos muchas dependencias?

Charles Graham
fuente
13
La inversión del control significa que "un objeto no debe saber cómo construir sus dependencias". Ese es nuevo para mí. No, realmente, eso no es lo que significa "inversión de control". Consulte martinfowler.com/bliki/InversionOfControl.html Ese artículo incluso proporciona referencias para la etimología del término, que data de la década de 1980.
Rogério
1
Mark Seemann argumenta que el Localizador de servicios es antipatrón ( blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern ). Además, el diagrama (que se encuentra aquí, stackoverflow.com/a/9503612/1977871 ) me resultó útil para comprender la situación de DI y SL. Espero que esto ayude.
VivekDev

Respuestas:

181

La diferencia puede parecer leve, pero incluso con ServiceLocator, la clase sigue siendo responsable de crear sus dependencias. Simplemente usa el localizador de servicios para hacerlo. Con DI, la clase tiene sus dependencias. No sabe ni le importa de dónde vienen. Un resultado importante de esto es que el ejemplo DI es mucho más fácil de realizar pruebas unitarias, porque puede pasar simulaciones de implementaciones de sus objetos dependientes. Puede combinar los dos e inyectar el localizador de servicios (o una fábrica), si lo desea.

tvanfosson
fuente
20
Además, puede usar ambos al construir una clase. El constructor predeterminado puede usar el SL para recuperar las dependencias y pasarlas al constructor "real" que recibe esas dependencias. Obtienes lo mejor de ambos mundos.
Grant Palin el
66
No, ServiceLocator es el responsable de crear instancias de la implementación correcta para una dependencia (complemento) determinada. En el caso de DI, el "contenedor" DI es el responsable de eso.
Rogério
55
@Rogerio sí, pero la clase aún tiene que saber sobre el Localizador de servicios ... eso son dos dependencias. Además, la mayoría de las veces he visto al Localizador de servicios delegar en el contenedor DI para buscar particularmente objetos transitorios que necesitan soporte técnico.
Adam Gent
2
@ Adam, no dije que el Localizador de servicios delegaría en un contenedor DI. Estos son dos patrones mutuamente excluyentes, como se describe en el artículo "oficial" . Para mí, el Localizador de servicios tiene una gran ventaja sobre la DI en la práctica: el uso de un contenedor de DI provoca abuso (que he visto repetidamente), mientras que el uso de un Localizador de servicios no.
Rogério
3
"Un resultado importante de esto es que el ejemplo de DI es mucho más fácil de probar unitario, porque puedes pasarlo simulando implementaciones de sus objetos dependientes". No es verdad. En sus pruebas unitarias, se puede usar una llamada a una función de registro en el contenedor del localizador de servicios para agregar fácilmente simulacros al registro.
Drumbeg
93

Cuando utiliza un localizador de servicios, cada clase dependerá de su localizador de servicios. Este no es el caso con la inyección de dependencia. El inyector de dependencia generalmente se llamará solo una vez en el inicio para inyectar dependencias en alguna clase principal. Las clases de las que depende esta clase principal se inyectarán recursivamente sus dependencias, hasta que tenga un gráfico de objeto completo.

Una buena comparación: http://martinfowler.com/articles/injection.html

Si su inyector de dependencia se parece a un localizador de servicios, donde las clases llaman directamente al inyector, probablemente no sea un inyector de dependencias, sino un localizador de servicios.

Joel
fuente
17
Pero, ¿cómo maneja el caso en el que tiene que crear objetos durante el tiempo de ejecución? Si los crea manualmente con "nuevo", no puede utilizar DI. Si llama al marco DI para obtener ayuda, está rompiendo el patrón. Entonces, ¿qué opciones quedan?
Boris
99
@ Boris Tuve el mismo problema y decidí inyectar fábricas específicas de clase. No es bonito pero hizo el trabajo. Me encantaría ver una solución más bonita.
Charlie Rudenstål
Enlace directo a la comparación: martinfowler.com/articles/…
Teoman shipahi
2
@ Boris Si tuviera que construir nuevos objetos sobre la marcha, inyectaría una Fábrica abstracta para dichos objetos. Lo que sería similar a inyectar un localizador de servicios en este caso, pero proporciona una interfaz concreta, uniforme y en tiempo de compilación para construir los objetos relevantes y hace explícitas las dependencias.
LivePastTheEnd
51

Los localizadores de servicios ocultan dependencias: al observar un objeto no se puede saber si golpea o no una base de datos (por ejemplo) cuando obtiene conexiones de un localizador. Con la inyección de dependencia (al menos la inyección del constructor) las dependencias son explícitas.

Además, los localizadores de servicios rompen la encapsulación porque proporcionan un punto de acceso global a las dependencias de otros objetos. Con el localizador de servicios, como con cualquier singleton :

se hace difícil especificar las condiciones previas y posteriores para la interfaz del objeto del cliente, porque el funcionamiento de su implementación puede entrometerse desde el exterior.

Con la inyección de dependencias, una vez que se especifican las dependencias de un objeto, están bajo el control del objeto mismo.

Jeff Sternal
fuente
3
Prefiero "Singletons Considered Stupid", steve.yegge.googlepages.com/singleton-considered-stupid
Charles Graham el
2
Me encanta Steve Yegge y el título de ese artículo es excelente, pero creo que el artículo que cité y "Singletons son mentirosos patológicos" de Miško Hevery ( misko.hevery.com/2008/08/17/singletons-are-pathological- mentirosos ) hacen un mejor caso contra el particular demonio del localizador de servicios.
Jeff Sternal
Esta respuesta es la más correcta porque define mejor un localizador de servicios: "Una clase que oculta sus dependencias". Tenga en cuenta que crear una dependencia internamente, aunque a menudo no es algo bueno, no convierte a una clase en un localizador de servicios. Además, tomar una dependencia de un contenedor es un problema pero no "el" problema que define más claramente un localizador de servicios.
Sam
1
With dependency injection (at least constructor injection) the dependencies are explicit.. Por favor explique.
FindOutIslamNow
Como arriba, no puedo ver cómo SL hace que las dependencias sean menos explícitas que DI ...
Michał Powłoka
38

Martin Fowler afirma :

Con el localizador de servicios, la clase de aplicación lo solicita explícitamente mediante un mensaje al localizador. Con la inyección no hay una solicitud explícita, el servicio aparece en la clase de aplicación, de ahí la inversión de control.

En resumen: el localizador de servicios y la inyección de dependencias son solo implementaciones del principio de inversión de dependencias.

El principio importante es "Depende de las abstracciones, no de las concreciones". Esto hará que su diseño de software esté "débilmente acoplado", "extensible", "flexible".

Puede usar el que mejor se adapte a sus necesidades. Para una aplicación grande, que tiene una gran base de código, es mejor que use un Localizador de servicios, porque la Inyección de dependencias requeriría más cambios en su base de código.

Puede consultar esta publicación: Inversión de dependencia: Localizador de servicio o Inyección de dependencia

También el clásico: inversión de contenedores de control y el patrón de inyección de dependencia por Martin Fowler

Diseño de clases reutilizables por Ralph E. Johnson y Brian Foote

Sin embargo, el que me abrió los ojos fue: ASP.NET MVC: ¿Resolver o inyectar? Ese es el problema ... por Dino Esposito

Nathan
fuente
Resumen fantástico: "El localizador de servicios y la inyección de dependencias son solo implementaciones del principio de inversión de dependencias".
Hans
Y también afirma: La diferencia clave es que con un Localizador de servicios, cada usuario de un servicio tiene una dependencia del localizador. El localizador puede ocultar dependencias a otras implementaciones, pero necesita ver el localizador. Entonces, la decisión entre el localizador y el inyector depende de si esa dependencia es un problema.
programaths
1
ServiceLocator y DI no tienen nada que ver con el "Principio de inversión de dependencia" (DIP). DIP es una forma de hacer que un componente de alto nivel sea más reutilizable, al reemplazar una dependencia en tiempo de compilación de un componente de bajo nivel con una dependencia de un tipo abstracto definido junto con el componente de alto nivel, que se implementa por componente de nivel; de esta manera, la dependencia del tiempo de compilación se invierte, ya que ahora es la de bajo nivel la que depende de la de alto nivel. Además, tenga en cuenta que el artículo de Martin Fowler explica que DI e IoC no son lo mismo.
Rogério
23

Una clase que usa el constructor DI indica al código consumidor que hay dependencias que deben satisfacerse. Si la clase usa el SL internamente para recuperar tales dependencias, el código consumidor no tiene conocimiento de las dependencias. Esto puede parecer mejor en la superficie, pero en realidad es útil saber de cualquier dependencia explícita. Es mejor desde una vista arquitectónica. Y al hacer pruebas, debe saber si una clase necesita ciertas dependencias y configurar el SL para proporcionar versiones falsas apropiadas de esas dependencias. Con DI, solo pasa las falsificaciones. No es una gran diferencia, pero está ahí.

Sin embargo, DI y SL pueden trabajar juntas. Es útil tener una ubicación central para dependencias comunes (por ejemplo, configuraciones, registrador, etc.). Dada una clase que usa tales deps, puede crear un constructor "real" que recibe los deps, y un constructor por defecto (sin parámetro) que recupera del SL y lo reenvía al constructor "real".

EDITAR: y, por supuesto, cuando usa el SL, está introduciendo algún acoplamiento a ese componente. Lo cual es irónico, ya que la idea de dicha funcionalidad es fomentar las abstracciones y reducir el acoplamiento. Las preocupaciones pueden ser equilibradas, y depende de cuántos lugares necesite usar el SL. Si se hace como se sugirió anteriormente, solo en el constructor de clase predeterminado.

Grant Palin
fuente
¡Interesante! Estoy usando tanto DI como SL, pero no con dos constructores. Tres o cuatro de las dependencias más aburridas que a menudo se necesitan (configuraciones, etc.) se obtienen de la SL sobre la marcha. Todo lo demás se inyecta por el constructor. Es un poco feo, pero pragmático.
maaartinus
10

Ambos son técnicas de implementación de IoC. También hay otros patrones que implementan Inversion of Control:

  • Patrón de fábrica
  • Servicio de localización
  • Contenedor DI (IoC)
  • Inyección de dependencias (inyección de constructor, inyección de parámetros (si no se requiere), inyección de setter de inyección de interfaz) ...

El localizador de servicios y el contenedor DI parecen más similares, ambos usan un contenedor para definir dependencias, que mapea la abstracción a la implementación concreta.

La principal diferencia es cómo se ubican las dependencias, en Service Locator, el código del cliente solicita las dependencias, en DI Container usamos un contenedor para crear todos los objetos e inyecta dependencia como parámetros (o propiedades) del constructor.

Nininea
fuente
7

En mi último proyecto utilizo ambos. Yo uso la inyección de dependencia para la capacidad de prueba de la unidad. Uso el localizador de servicios para ocultar la implementación y ser dependiente de mi contenedor de IoC. ¡y si! Una vez que utiliza uno de los contenedores de IoC (Unity, Ninject, Windsor Castle), depende de él. Y una vez que esté desactualizado o, por alguna razón, si desea intercambiarlo, tendrá que cambiar su implementación, al menos la raíz de la composición. Pero el localizador de servicios abstrae esa fase.

¿Cómo no dependería de su contenedor de IoC? O deberá envolverlo usted mismo (lo cual es una mala idea) o utilizar el Localizador de servicios para configurar su contenedor IoC. Entonces le dirá al localizador de servicios que obtenga la interfaz que necesita, y llamará al contenedor IoC configurado para recuperar esa interfaz.

En mi caso, uso ServiceLocator, que es un componente de marco. Y use Unity para el contenedor IoC. Si en el futuro necesito cambiar mi contenedor de IoC a Ninject todo lo que necesito hacer es configurar mi localizador de servicios para usar Ninject en lugar de Unity. Fácil migración

Aquí hay un gran artículo que explica este escenario; http://www.johandekoning.nl/index.php/2013/03/03/dont-wrap-your-ioc-container/

Teoman shipahi
fuente
El enlace del artículo johandekoning está roto.
JakeJ
6

Creo que los dos trabajan juntos.

La inyección de dependencia significa que empuja alguna clase / interfaz dependiente a una clase consumidora (generalmente a su constructor). Esto desacopla las dos clases a través de una interfaz y significa que la clase consumidora puede trabajar con muchos tipos de implementaciones de "dependencia inyectada".

El rol del localizador de servicios es reunir su implementación. Configura un localizador de servicios mediante algunas correas de arranque al inicio de su programa. Bootstrapping es el proceso de asociar un tipo de implementación a un resumen / interfaz en particular. Que se crea para ti en tiempo de ejecución. (basado en su configuración o bootstrap). Si no hubiera implementado la inyección de dependencia, sería muy difícil utilizar un localizador de servicios o un contenedor IOC.

NoelAdy
fuente
6

Una razón para agregar, inspirada en una actualización de documentación que escribimos para el proyecto MEF la semana pasada (ayudo a construir MEF).

Una vez que una aplicación se compone de potencialmente miles de componentes, puede ser difícil determinar si algún componente en particular se puede instanciar correctamente. Por "instanciado correctamente", quiero decir que en este ejemplo basado en el Foocomponente, una instancia de IBary estará disponible, y que el componente que lo proporciona:

  • tiene sus dependencias requeridas,
  • no estar involucrado en ningún ciclo de dependencia inválido, y
  • en el caso de MEF, se suministrará con una sola instancia.

En el segundo ejemplo que dio, donde el constructor va al contenedor de IoC para recuperar sus dependencias, la única forma en que puede probar que una instancia de Foose podrá instanciar correctamente con la configuración de tiempo de ejecución real de su aplicación es construir realmente eso .

Esto tiene todo tipo de efectos secundarios incómodos en el momento de la prueba, porque el código que funcionará en tiempo de ejecución no necesariamente funcionará bajo un arnés de prueba. Los simulacros no funcionarán, porque la configuración real es lo que necesitamos probar, no una configuración de tiempo de prueba.

La raíz de este problema es la diferencia ya señalada por @Jon: la inyección de dependencias a través del constructor es declarativa, mientras que la segunda versión usa el patrón imperativo del Localizador de servicios.

Un contenedor de IoC, cuando se usa con cuidado, puede analizar estáticamente la configuración de tiempo de ejecución de su aplicación sin crear realmente ninguna instancia de los componentes involucrados. Muchos contenedores populares proporcionan alguna variación de esto; Microsoft.Composition , que es la versión de MEF dirigida a aplicaciones web y de estilo Metro .NET 4.5, proporciona una CompositionAssertmuestra en la documentación wiki. Al usarlo, puede escribir código como:

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

CompositionAssert.CanExportSingle<Foo>(container);

(Ver este ejemplo ).

Al verificar las Raíces de composición de su aplicación en el momento de la prueba, puede detectar algunos errores que, de lo contrario, podrían pasar por alto las pruebas más adelante en el proceso.

¡Espero que esta sea una adición interesante a este conjunto de respuestas sobre el tema!

Nicholas Blumhardt
fuente
5

Nota: no estoy respondiendo exactamente la pregunta. Pero creo que esto puede ser útil para los nuevos aprendices del patrón de Inyección de dependencia que están confundidos con el patrón Localizador de servicios (anti) que tropiezan con esta página.

Sé la diferencia entre el Localizador de servicios (parece que ahora se considera como un antipatrón) y los patrones de Inyección de dependencias y puedo entender ejemplos concretos de cada patrón, pero me confundieron los ejemplos que muestran un localizador de servicios dentro del constructor (supongamos que ' re haciendo inyección de constructor).

El "Localizador de servicios" a menudo se usa como el nombre de un patrón y como el nombre para referirse al objeto (supongamos también) usado en ese patrón para obtener objetos sin usar el nuevo operador. Ahora, ese mismo tipo de objeto también se puede usar en la raíz de la composición para realizar una inyección de dependencia, y ahí es donde entra la confusión.

Lo importante es que puede estar utilizando un objeto localizador de servicios dentro de un constructor DI, pero no está utilizando el "patrón Localizador de servicios". Es menos confuso si uno lo refiere como un objeto contenedor de IoC, ya que puede haber adivinado que esencialmente hacen lo mismo (corríjame si me equivoco).

Si se conoce como un localizador de servicios (o solo un localizador), o como un contenedor de IoC (o solo contenedor) no hace ninguna diferencia, ya que supones que probablemente se refieren a la misma abstracción (corrígeme si me equivoco ) Es solo que llamarlo un localizador de servicios sugiere que uno está usando el antipatrón Localizador de servicios junto con el patrón de Inyección de dependencias.

En mi humilde opinión, nombrarlo como 'localizador' en lugar de 'ubicación' o 'ubicación', también puede hacer que uno piense a veces que el localizador de servicios en un artículo se refiere al contenedor del Localizador de servicios, y no al patrón Localizador de servicios (anti) , especialmente cuando hay un patrón relacionado llamado Inyección de dependencia y no Inyector de dependencia.

blizpasta
fuente
4

En este caso excesivamente simplificado, no hay diferencia y se pueden usar indistintamente. Sin embargo, los problemas del mundo real no son tan simples. Simplemente suponga que la clase Bar en sí tenía otra dependencia llamada D. En ese caso, su localizador de servicios no podría resolver esa dependencia y tendría que crear una instancia dentro de la clase D; porque es responsabilidad de sus clases instanciar sus dependencias. Incluso empeoraría si la clase D en sí tuviera otras dependencias y, en situaciones del mundo real, generalmente se vuelve aún más complicado que eso. En tales escenarios, DI es una mejor solución que ServiceLocator.

Daniel
fuente
44
Hmm, no estoy de acuerdo: el localizador de servicios ex. muestra claramente que todavía hay una dependencia allí ... el localizador de servicios. Si la barclase en sí misma tiene una dependencia, bartambién tendrá un localizador de servicios, ese es el punto de usar DI / IoC.
GFoley83
2

¿Cuál es la diferencia (si la hay) entre Inyección de dependencias y Localizador de servicios? Ambos patrones son buenos para implementar el principio de Inversión de dependencia. El patrón del Localizador de servicios es más fácil de usar en una base de código existente, ya que hace que el diseño general sea más flexible sin forzar cambios en la interfaz pública. Por esta misma razón, el código que se basa en el patrón del Localizador de servicios es menos legible que el código equivalente que se basa en la Inyección de dependencias.

El patrón de inyección de dependencias deja en claro desde la firma qué dependencias tendrá una clase (o un método). Por esta razón, el código resultante es más limpio y más legible.

Yogesh
fuente
1

Seguir una concepción simple me dio una comprensión más clara de la diferencia entre el Localizador de servicios y el Contenedor DI:

  • Service Locator se usa en el consumidor y extrae los servicios por ID de algún almacenamiento por solicitud directa del consumidor

  • DI Container está ubicado en algún lugar afuera y toma servicios de algún almacenamiento y los envía al consumidor (no importa a través del constructor o el método)

Sin embargo, podemos hablar sobre la diferencia entre estos solo en el contexto del uso concreto del consumidor. Cuando Service Locator y DI Container se utilizan en la raíz de la composición, son casi similares.

romano
fuente
0

El contenedor DI es un superconjunto de localizador de servicios. Se puede usar para localizar un servicio , con capacidad adicional de ensamblar (cablear) las inyecciones de dependencia .

Glenn Mohammad
fuente
-2

Para el registro

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

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

  //...
}

A menos que realmente necesite una interfaz (la interfaz es utilizada por más de una clase), NO DEBE USARLA . En este caso, IBar permite utilizar cualquier clase de servicio que la implemente. Sin embargo, por lo general, esta interfaz será utilizada por una sola clase.

¿Por qué es una mala idea usar una interfaz? Porque es realmente difícil de depurar.

Por ejemplo, supongamos que la instancia "bar" falló, pregunta: ¿ qué clase falló? ¿Qué código debo arreglar? Una vista simple, conduce a una interfaz, y es aquí donde termina mi camino.

En cambio, si el código usa una dependencia rígida, entonces es fácil depurar un error.

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

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

  //...
}

Si "bar" falla, entonces debería verificar y activar la clase BarService.

magallanes
fuente
1
Una clase es un plan para construir un objeto específico. Por otro lado, una interfaz es contracty simplemente define un comportamiento, no la acción. En lugar de pasar alrededor del objeto real, solo se comparte la interfaz para que el consumidor no acceda al resto de su objeto. También para las pruebas unitarias, ayuda probar solo la parte que necesita ser probada. Supongo que a tiempo entenderías su utilidad.
Gunhan