Entiendo que instanciar directamente dependencias dentro de una clase se considera una mala práctica. Esto tiene sentido, ya que al hacerlo se acopla todo lo que a su vez hace que las pruebas sean muy difíciles.
Casi todos los marcos que he encontrado parecen favorecer la inyección de dependencia con un contenedor en lugar de usar localizadores de servicios. Ambos parecen lograr lo mismo al permitir que el programador especifique qué objeto debe devolverse cuando una clase requiere una dependencia.
¿Cuál es la diferencia entre los dos? ¿Por qué elegiría uno sobre el otro?
dependency-injection
ioc-containers
service-locator
tom6025222
fuente
fuente
Respuestas:
Cuando el propio objeto es responsable de solicitar sus dependencias, en lugar de aceptarlas a través de un constructor, está ocultando información esencial. Es solo un poco mejor que el caso de uso muy acoplado
new
para crear instancias de sus dependencias. Reduce el acoplamiento porque, de hecho, puede cambiar las dependencias que obtiene, pero todavía tiene una dependencia que no puede sacudir: el localizador de servicios. Eso se convierte en lo que todo depende.Un contenedor que proporciona dependencias a través de argumentos de constructor proporciona la mayor claridad. Vemos por adelantado que un objeto necesita tanto an
AccountRepository
como aPasswordStrengthEvaluator
. Cuando se utiliza un localizador de servicios, esa información es menos evidente de inmediato. Vería de inmediato un caso en el que un objeto tiene, oh, 17 dependencias, y se dirá a sí mismo: "Hmm, eso parece mucho. ¿Qué está pasando allí?" Las llamadas a un localizador de servicios pueden extenderse en torno a los diversos métodos y esconderse detrás de la lógica condicional, y es posible que no se dé cuenta de que ha creado una "clase de Dios", una que hace todo. Tal vez esa clase se podría refactorizar en 3 clases más pequeñas que están más enfocadas y, por lo tanto, son más comprobables.Ahora considere realizar pruebas. Si un objeto usa un localizador de servicios para obtener sus dependencias, su marco de prueba también necesitará un localizador de servicios. En una prueba, configurará el localizador de servicios para suministrar las dependencias al objeto bajo prueba, tal vez ay
FakeAccountRepository
aVeryForgivingPasswordStrengthEvaluator
, y luego ejecutará la prueba. Pero eso es más trabajo que especificar dependencias en el constructor del objeto. Y su marco de prueba también se vuelve dependiente del localizador de servicios. Es otra cosa que debe configurar en cada prueba, lo que hace que escribir pruebas sea menos atractivo.Busque "El localizador de servicios es un antipatrón" para el artículo de Mark Seeman al respecto. Si estás en el mundo .Net, consigue su libro. Es muy bueno.
fuente
constructor supplied dependencies
contraservice locator
es que el primero se puede verfied tiempo de compilación, mientras que el segundo puede solamente ser verificada en tiempo de ejecución.But that's more work than specifying dependencies in the object's constructor.
me gustaría objetar. Con un localizador de servicios solo necesita especificar las 3 dependencias que realmente necesita para su prueba. Con un DI basado en el constructor, debe especificar TODOS los 10, incluso si 7 no se utilizan.Imagina que eres un trabajador en una fábrica que fabrica zapatos .
Usted es responsable de armar los zapatos, por lo que necesitará muchas cosas para hacerlo.
Y así.
Estás trabajando en la fábrica y estás listo para comenzar. Tiene una lista de instrucciones sobre cómo proceder, pero todavía no tiene ninguno de los materiales o herramientas.
Un localizador de servicios es como un capataz que puede ayudarlo a obtener lo que necesita.
Usted le pregunta al Localizador de servicios cada vez que necesita algo, y se van a buscarlo. El Localizador de servicios ha sido informado con anticipación sobre lo que es probable que solicite y cómo encontrarlo.
Sin embargo, será mejor que no pidas algo inesperado. Si el Localizador no ha sido informado con anticipación sobre una herramienta o material en particular, no podrá obtenerlo por usted, y simplemente se encogerán de hombros.
Un contenedor de inyección de dependencia (DI) es como una gran caja que se llena con todo lo que todos necesitan al comienzo del día.
Cuando se inicia la fábrica, el Big Boss conocido como la raíz de composición toma el contenedor y entrega todo a los gerentes de línea .
Los gerentes de línea ahora tienen lo que necesitan para realizar sus tareas del día. Toman lo que tienen y pasan lo que necesitan a sus subordinados.
Este proceso continúa, con dependencias goteando por la línea de producción. Finalmente, se muestra un contenedor de materiales y herramientas para su capataz.
Su capataz ahora distribuye exactamente lo que necesita para usted y otros trabajadores, sin siquiera pedirlos.
Básicamente, tan pronto como te presentas para trabajar, todo lo que necesitas ya está allí en una caja esperándote. No necesitabas saber nada sobre cómo conseguirlos.
fuente
Un par de puntos extra que he encontrado al recorrer la web:
fuente
Llego tarde a esta fiesta pero no puedo resistirme.
A veces ninguno en absoluto. Lo que hace la diferencia es lo que sabe sobre qué.
Usted sabe que está utilizando un localizador de servicios cuando el cliente que busca la dependencia conoce el contenedor. Un cliente que sabe cómo encontrar sus dependencias, incluso cuando atraviesa un contenedor para obtenerlas, es el patrón del localizador de servicios.
¿Esto significa que si desea evitar el localizador de servicios no puede usar un contenedor? No. Solo tiene que evitar que los clientes sepan sobre el contenedor. La diferencia clave es dónde usa el contenedor.
Digamos
Client
necesidadesDependency
. El contenedor tiene aDependency
.Acabamos de seguir el patrón de localización del servicio porque
Client
sabe cómo encontrarloDependency
. Claro que usa un código rígido,ClassPathXmlApplicationContext
pero incluso si inyecta que todavía tiene un localizador de servicio porqueClient
llamabeanfactory.getBean()
.Para evitar el localizador de servicios, no tiene que abandonar este contenedor. Solo tienes que sacarlo
Client
paraClient
que no lo sepas.Observe cómo
Client
ahora no tiene idea de que el contenedor existe:Mueva el contenedor fuera de todos los clientes y péguelo en main donde puede construir un gráfico de objetos de todos sus objetos de larga vida. Elija uno de esos objetos para extraer y llame a un método y comience a marcar todo el gráfico.
Eso mueve toda la construcción estática a los contenedores XML pero mantiene a todos sus clientes felizmente ignorantes de cómo encontrar sus dependencias.
¡Pero main todavía sabe cómo localizar dependencias! Si lo hace Pero al no difundir ese conocimiento, ha evitado el problema central del localizador de servicios. La decisión de usar un contenedor ahora se toma en un solo lugar y podría cambiarse sin tener que reescribir a cientos de clientes.
fuente
Creo que la forma más fácil de entender la diferencia entre los dos y por qué un contenedor DI es mucho mejor que un localizador de servicios es pensar por qué hacemos la inversión de dependencia en primer lugar.
Hacemos una inversión de dependencia para que cada clase explícitamente indique exactamente de qué depende para funcionar. Lo hacemos porque esto crea el acoplamiento más flojo que podemos lograr. Cuanto más flojo es el acoplamiento, más fácil es probar y refactorizar (y generalmente requiere la menor refactorización en el futuro porque el código es más limpio).
Veamos la siguiente clase:
En esta clase, declaramos explícitamente que necesitamos un IOutputProvider y nada más para que esta clase funcione. Esto es totalmente comprobable y depende de una única interfaz. Puedo mover esta clase a cualquier parte de mi aplicación, incluido un proyecto diferente y todo lo que necesita es acceso a la interfaz IOutputProvider. Si otros desarrolladores desean agregar algo nuevo a esta clase, que requiere una segunda dependencia, tienen que ser explícitos sobre lo que necesitan en el constructor.
Eche un vistazo a la misma clase con un localizador de servicios:
Ahora he agregado el localizador de servicios como la dependencia. Aquí están los problemas que son inmediatamente obvios:
Entonces, ¿por qué no hacemos que el localizador de servicios sea una clase estática? Vamos a ver:
Esto es mucho más simple, ¿verdad?
Incorrecto.
Digamos que IOutputProvider está implementado por un servicio web de muy larga duración que escribe la cadena en quince bases de datos diferentes en todo el mundo y tarda mucho tiempo en completarse.
Intentemos probar esta clase. Necesitamos una implementación diferente de IOutputProvider para la prueba. ¿Cómo escribimos el examen?
Bueno, para hacer eso necesitamos hacer una configuración elegante en la clase estática ServiceLocator para usar una implementación diferente de IOutputProvider cuando la prueba lo invoca. Incluso escribir esa oración fue doloroso. Implementarlo sería tortuoso y sería una pesadilla de mantenimiento . Nunca deberíamos necesitar modificar una clase específicamente para pruebas, especialmente si esa clase no es la clase que realmente estamos tratando de probar.
Así que ahora te queda con a) una prueba que está causando cambios de código molestos en la clase ServiceLocator no relacionada; o b) ninguna prueba en absoluto. Y también te queda una solución menos flexible.
Así la clase localizador de servicios tiene que ser inyectado en el constructor. Lo que significa que nos quedamos con los problemas específicos mencionados anteriormente. El localizador de servicios requiere más código, le dice a otros desarrolladores que necesita cosas que no necesita, alienta a otros desarrolladores a escribir un código peor y nos da menos flexibilidad para avanzar.
En pocas palabras, los localizadores de servicios aumentan el acoplamiento en una aplicación y alientan a otros desarrolladores a escribir código altamente acoplado .
fuente