design patterns - locator - ¿Cuál es la diferencia entre los patrones de inyección de dependencias y de localización de servicios?
service locator pattern (13)
Ambos patrones parecen una implementación del principio de inversión de control. Es decir, que un objeto no debe saber construir sus dependencias.
La inyección de dependencia (DI) parece utilizar un constructor o configurador para "inyectar" sus dependencias.
Ejemplo de uso de Inyección Constructor:
//Foo Needs an IBar
public class Foo
{
private IBar bar;
public Foo(IBar bar)
{
this.bar = bar;
}
//...
}
Service Locator parece utilizar un "contenedor", que conecta sus dependencias y le da a foo 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ó el Contenedor de Inversión de Control (o Contenedor DI). Ejemplos: Castle Windsor, Ninject, Structure Map, Spring, etc.)
Pero un contenedor IOC / DI se ve exactamente como un localizador de servicios. ¿Llamar a un contenedor DI un mal nombre? ¿Un contenedor IOC / DI es otro tipo de localizador de servicios? ¿Está el matiz en el hecho de que usamos contenedores DI principalmente cuando tenemos muchas Dependencias?
¿Cuál es la diferencia (si existe) entre la inyección de dependencia y el 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 este mismo motivo, 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 dependencia.
El patrón de inyección de dependencias lo deja claro ya que la firma que dependencias tendrá una clase (o un método). Por esta razón, el código resultante es más limpio y más legible.
Ambas son técnicas de implementación de la IoC. También hay otros patrones que implementan Inversión de Control:
- patrón de fábrica
- localizador de servicios
- inyección de dependencia (inyección de constructor, inyección de parámetros (si no es necesaria), inyección de incubadora de inyección de interfaz) ...
El localizador de servicios y el 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 el Código del cliente de Ubicación del Servicio, las dependencias, en DI usamos el contenedor para crear todos los objetos e inyecta la dependencia como parámetros (o propiedades) del constructor.
Creo que los dos trabajan juntos.
La inyección de dependencia significa que usted empuja en 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. Al iniciar el programa, configura un localizador de servicios mediante algunos ajustes de arranque. Bootstrapping es el proceso de asociar un tipo de implementación a un resumen / interfaz particular. Que se crea para usted en tiempo de ejecución. (basado en tu 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.
Cuando use un localizador de servicios, cada clase tendrá una dependencia de su localizador de servicios. Este no es el caso de la inyección de dependencia. El inyector de dependencia normalmente se llamará solo una vez en el inicio para inyectar dependencias en alguna clase principal. Las clases de las que depende esta clase principal tendrán sus dependencias inyectadas de forma recursiva, hasta que tenga un gráfico de objetos 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 al inyector directamente, probablemente no sea un inyector de dependencias, sino un localizador de servicios.
En este caso demasiado 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 tenía otra dependencia llamada D. En ese caso, su localizador de servicio no podría resolver esa dependencia y tendría que crear una instancia dentro de la clase D; Porque es responsabilidad de tus clases instanciar sus dependencias. Incluso empeoraría si la clase D tuviera otras dependencias y, en situaciones del mundo real, por lo general se complica aún más. En tales escenarios, DI es una solución mejor que ServiceLocator.
En mi último proyecto uso ambos. Yo uso la inyección de dependencia para la unidad de prueba. Utilizo el localizador de servicios para ocultar la implementación y ser dependiente de mi contenedor IoC. ¡y si! Una vez que use uno de los contenedores IoC (Unity, Ninject, Windsor Castle) dependerá de ello. Y una vez que esté desactualizado o por algún motivo, 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ías de tu contenedor IoC? O deberá envolverlo por su cuenta (lo cual es una mala idea) o usar el Localizador de servicios para configurar su contenedor de IoC. Por lo tanto, 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 usa Unity para el contenedor IoC. Si en el futuro tengo que cambiar mi contenedor de IoC a Ninject todo lo que debo hacer es configurar mi localizador de servicio 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/
La diferencia puede parecer leve, pero incluso con el ServiceLocator, la clase sigue siendo responsable de crear sus dependencias. Solo usa el localizador de servicios para hacerlo. Con DI, a la clase se le dan sus dependencias. No sabe, ni le importa de dónde vienen. Un resultado importante de esto es que el ejemplo de DI es mucho más fácil de realizar una prueba unitaria, ya que puede pasar implementaciones simuladas de sus objetos dependientes. Puede combinar los dos e inyectar el localizador de servicios (o una fábrica), si así lo desea.
Los localizadores de servicios ocultan las dependencias: no se puede saber mirando un objeto si llega a una base de datos o no (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 global de acceso a las dependencias de otros objetos. Con localizador de servicios, como con cualquier singleton :
resulta difícil especificar las condiciones previas y posteriores para la interfaz del objeto cliente, ya que el funcionamiento de su implementación puede ser entrometido desde el exterior.
Con la inyección de dependencia, una vez que se especifican las dependencias de un objeto, están bajo el control del objeto en sí.
Nota: No estoy respondiendo exactamente a la pregunta. Pero creo que esto puede ser útil para los nuevos estudiantes del patrón de inyección de dependencia que están confundidos al respecto con el patrón de localizador de servicio (anti) que se topa con esta página.
Sé que la diferencia entre el Localizador de servicios (parece que ahora se considera un antipatrón) y los Patrones de inyección de dependencia y puedo entender ejemplos concretos de cada patrón, sin embargo, me confundieron los ejemplos que muestran un localizador de servicios dentro del constructor (supongamos que haciendo inyección de constructor).
El "Localizador de servicios" se usa a menudo tanto como el nombre de un patrón, como el nombre para referirse al objeto (también se supone) utilizado 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 la inyección de dependencia, y ahí es donde entra la confusión.
El punto a tener en cuenta es que puede estar usando un objeto localizador de servicios dentro de un constructor DI, pero no está usando el "patrón Localizador de servicios". Es menos confuso si uno lo refiere como un objeto contenedor IoC, ya que puede haber adivinado que esencialmente hacen lo mismo (corríjame si me equivoco).
Ya sea que se haga referencia como un localizador de servicios (o simplemente un localizador), o como un contenedor IoC (o solo un contenedor) no hace ninguna diferencia como supones, ya que probablemente se refieran 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ónico del localizador de servicios junto con el patrón de inyección de dependencias.
En mi humilde opinión, nombrarlo "localizador" en lugar de "ubicación" o "localización", también puede hacer que uno piense a veces que el localizador de servicios en un artículo se refiere al contenedor Localizador de servicios, y no al patrón (anti-) del Localizador de servicios , especialmente cuando hay un patrón relacionado llamado Inyección de dependencia y no Inyector de dependencia.
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 UTILIZARLA . En este caso, IBar permite utilizar cualquier clase de servicio, que lo 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, digamos que la instancia "barra" 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 utiliza una dependencia difícil, 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 la "barra" falla, debería verificar y disparar la clase BarService.
Una clase que usa el constructor DI indica a un código de consumo que hay dependencias que deben satisfacerse. Si la clase utiliza la SL internamente para recuperar dichas dependencias, el código consumidor no es consciente de las dependencias. Esto puede parecer mejor en la superficie, pero en realidad es útil saber de cualquier dependencia explícita. Es mejor desde un punto de vista arquitectónico. Y cuando realice pruebas, debe saber si una clase necesita ciertas dependencias y configurar el SL para proporcionar versiones falsas apropiadas de esas dependencias. Con DI, acaba de pasar en las falsificaciones. No hay una gran diferencia, pero está ahí.
Sin embargo, DI y SL pueden trabajar juntos. Es útil tener una ubicación central para las dependencias comunes (por ejemplo, configuraciones, registrador, etc.). Dada una clase que usa tales deps, puede crear un constructor "real" que reciba los deps, y un constructor predeterminado (sin parámetro) que recupere del SL y lo envíe al constructor "real".
EDITAR: y, por supuesto, cuando usa la SL, está introduciendo algún acoplamiento a ese componente. Lo que es irónico, ya que la idea de tal funcionalidad es fomentar las abstracciones y reducir el acoplamiento. Las preocupaciones pueden ser equilibradas, y depende de cuántos lugares necesitaría usar el SL. Si se hace como se sugirió anteriormente, solo en el constructor de clase predeterminado.
Una razón para agregar, inspirada en una actualización de la documentación que escribimos para el proyecto MEF la semana pasada (ayudo a construir MEF).
Una vez que una aplicación está compuesta de miles de componentes, puede ser difícil determinar si un componente en particular puede ser instanciado correctamente. Por "instanciado correctamente", quiero decir que en este ejemplo basado en el componente Foo
, una instancia de IBar
y estará disponible, y que el componente que lo proporcione:
- tienen 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 manera de probar que una instancia de Foo
podrá instanciarse correctamente con la configuración de tiempo de ejecución real de su aplicación es realmente construirlo
Esto tiene todo tipo de efectos secundarios incómodos en el momento de la prueba, porque el código que funcionará en el tiempo de ejecución no funcionará necesariamente bajo un arnés de prueba. Los simulacros no sirven, 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 mencionada por @Jon: la inyección de dependencias a través del constructor es declarativa, mientras que la segunda versión usa el imperativo patrón del Localizador de servicios.
Un contenedor 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 las aplicaciones web y estilo Metro de .NET 4.5, proporciona un ejemplo de CompositionAssert
en la documentación de la wiki. Usándolo, puedes 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 las pruebas más adelante en el proceso.
Espero que esta sea una adición interesante a este conjunto de respuestas por lo demás exhaustivas sobre el tema.
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 la aplicación, de ahí la inversión del control.
En resumen: el Localizador de Servicios y la Inyección de Dependencias son solo implementaciones del Principio de Inversión de Dependencia.
El principio importante es "Depender de abstracciones, no de concreciones". Esto hará que su diseño de software sea "acoplado libremente", "extensible", "flexible".
Puedes usar el que mejor se adapte a tus necesidades. Para una aplicación grande, con una base de código enorme, es mejor que uses un Localizador de Servicio, porque la Inyección de Dependencia requeriría más cambios en tu 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: contenedores de inversión de control y el patrón de inyección de dependencia por Martin Fowler
Diseñando Clases Reutilizables por Ralph E. Johnson & Brian Foote
Sin embargo, el que abrió mis ojos fue: ASP.NET MVC: ¿Resolver o inyectar? Ese es el problema ... por Dino Esposito