2009-02-01 17 views
39

Esto podría ser una pregunta ingenua. Actualmente estoy aprendiendo el marco Spring y la inyección de dependencia . Si bien el principio básico de DI es bastante fácil de comprender, no es inmediatamente obvio por qué se necesita un marco elaborado para implementarlo.Comprender la necesidad de un marco DI

considerar lo siguiente:

public abstract class Saw 
{ 
    public abstract void cut(String wood); 
} 

public class HandSaw extends Saw 
{ 
    public void cut(String wood) 
    { 
     // chop it up 
    } 
} 

public class ChainSaw extends Saw 
{ 
    public void cut(String wood) 
    { 
     // chop it a lot faster 
    } 
} 

public class SawMill 
{ 
    private Saw saw; 

    public void setSaw(Saw saw) 
    { 
     this.saw = saw; 
    } 

    public void run(String wood) 
    { 
     saw.cut("some wood"); 
    } 
} 

Posteriormente, se podría simplemente hacer:

Saw saw = new HandSaw(); 
SawMill sawMill = new SawMill(); 
sawMill.setSaw(saw); 
sawMill.run(); 

lo que sería equivalente a:

<bean id="saw" class="HandSaw"/> 

<bean id="sawMill" class="SawMill"> 
    <property name="saw" ref="saw"/> 
</bean> 

más:

ApplicationContext context = new ClassPathXmlApplicationContext("sawmill.xml"); 
SawMill springSawMill = (SawMill)context.getBean("sawMill"); 
springSawMill.run(); 

Concedido, este es un ejemplo de contribución, y con relaciones de objeto más complejas podría ser más eficiente almacenar un archivo XML que escribirlo programáticamente, pero seguramente debe haber más que eso.

(Sé el marco de Primavera es más que eso, pero estoy pensando en la necesidad de un contenedor de DI.)

En el primer ejemplo, también sería trivial para cambiar las dependencias de la mitad del chorro:

// gotta chop it faster 
saw = new ChainSaw(); 
sawMill.setSaw(saw); 
sawMill.run(); 
+0

Esto es muy similar a http://stackoverflow.com/questions/131975. –

+0

El objetivo principal de DI es facilitar la configuración de las aplicaciones y hacer las pruebas más fáciles/posibles (DI le permite intercambiar dependencias fácilmente) –

Respuesta

2

Si codifica la clase insertada, necesita que la clase esté disponible en tiempo de compilación. Con un archivo de configuración, puede cambiar la sierra utilizada (en su caso) en tiempo de ejecución sin volver a compilar e incluso utilizar una sierra extraída de un nuevo contenedor que acaba de colocar en el classpath. Si vale la pena, la complejidad adicional depende de la tarea que tenga que resolver.

+1

Eso no tiene nada que ver con DI. – Paco

+1

Ergo, el propósito de un archivo de configuración es vencer el sistema de tipo. – Apocalisp

+0

@Paco: es curioso que diga eso, porque la propia definición de DI (http://martinfowler.com/articles/injection.html) lo describe como una forma de "separar la configuración del uso", que se supone que es útil precisamente porque puede cambiar la implementación utilizada en el tiempo de ejecución sin cambiar (y volver a compilar) el código del cliente. –

1

Es importante entender que la primavera es fundamentalmente dos cosas, una construida en la cima de la otra:

  1. un marco ligero DI/COI y las clases para implementar ese (por ejemplo, los contextos de aplicación XML, etc.); y
  2. Es un contenedor liviano .

(2) es la mayor parte del código de Spring. Básicamente elige una tecnología Java y probablemente encontrarás que Spring tiene clases de ayuda para ello. Esto es para que pueda usar ActiveMQ, Sun One MQ o lo que sea y abstraerlos como Spring JmsTemplate y lo mismo ocurre con las tecnologías de acceso a datos, servicios web, etc.

Todos estos ayudantes usan (1) para unirlos .

15

He tenido exactamente la misma pregunta, y fue respondida por esto:
De acuerdo, podría hacer lo que ha descrito en "Entonces simplemente podría hacer: ..." UN"). Sin embargo, eso juntaría la clase A con HandSaw, o con todas las dependencias necesarias de la clase SawMill. ¿Por qué A debe estar acoplado a HandSaw? O, si toma un escenario más realista, ¿por qué mi lógica comercial debe estar acoplada a la implementación de la conexión JDBC necesaria para la capa DAO?
La solución que propuse entonces fue "luego mover las dependencias un paso más allá" - ok, así que ahora tengo mi vista acoplada a la conexión JDBC, donde debería tratar solo con HTML (o Swing, elija su sabor).

marco de la DI, configurado por un XML (o JavaConfig) soluciona este problema al permitir que acaba de "obtener el servicio necesario". No te importa cómo se inicializa, qué necesita para funcionar, simplemente obtienes el objeto de servicio y lo activas.

Además, tiene una idea errónea sobre el "más:" (donde hace SawMill springSawMill = (SawMill)context.getBean("sawMill"); springSawMill.run();) - no necesita obtener el grano de sierra para asar del contexto - el aserrín debe haber sido inyectado en su objeto (clase A) por el marco DI. así que en lugar de ... getBean (...), simplemente iría a "sawMill.run()", sin importar de dónde venía, quién lo inicializó y cómo. Por lo que a usted le importa, podría ir directamente a/dev/null, o a una salida de prueba, o un motor CnC real ... El punto es que no le importa. Lo único que le importa es su pequeña clase A que debería hacer lo que tiene contratado: activar un aserradero.

+2

Esta respuesta contradice la definición "oficial" (http://martinfowler.com/articles/injection.html) del patrón de Inyección de Dependencia, a mi entender. La clase "A" acoplada a la clase "HandSaw" es un problema SOLO si la clase real que implementa la abstracción "Saw" debe elegirse en el tiempo de ejecución. De lo contrario, está perfectamente bien instanciar directamente una clase de implementación de Saw en el código del cliente. DI se trata realmente de "separar la configuración del uso" en aquellas situaciones en las que realmente se necesita, no en todas partes de forma predeterminada. –

+6

Es cierto solo hasta que comience la prueba. Entonces desearía tener alguna forma de poner "TestSawImpl", que solo afirma, en A en lugar de una "Mano de mano" real, que requiere una pila de madera, una fuente de energía y un operador de sierra certificada (y probablemente un médico de guardia) . –

+3

No, siempre es cierto. Puedo escribir pruebas unitarias fácilmente mediante el uso de una herramienta de burla. No es necesario crear un 'TestSawImpl' porque la herramienta de burla me permite simular cualquier clase de implementación' Saw' con una sola línea de código, incluso si no se conoce la clase concreta en tiempo de compilación. –

5

Por lo general, no me importa la DI basada en XML o Reflection porque, en mis casos de uso, agrega una complejidad innecesaria. En lugar de eso, generalmente busco algún tipo de DI manual que, para mí, parezca más natural y tenga la mayoría de los beneficios.

public class SawDI{ 
    public Saw CreateSaw(){ 
     return new HandSaw(); 
    } 

    public SawMill CreateSawMill(){ 
     SawMill mill = new SawMill(); 
     mill.SetSaw(CreateSaw()); 
     return mill; 
    } 
} 

// later on 

SawDI di = new SawDI(); 
SawMill mill = di.CreateSawMill(); 

Esto significa que todavía centralizar el acoplamiento y tienen todas las ventajas de que, sin la dependencia de marco DI más complejos o los archivos de configuración XML.

+0

Esto es mucho trabajo de tipado repetido en comparación con el uso de un contenedor de inversión de control – Paco

+0

Tendrá que escribir casi lo mismo en los archivos de configuración XML que en este caso la mayoría de las veces. –

+0

Y en el caso de la DI basada en la reflexión, este enfoque es mucho más transparente para cualquiera que use el sistema. –

3

Una cosa que la mayoría (si no todos) los recipientes DI/bibliotecas lograr que además es la posibilidad de interceptar métodos para todas las instancias creadas a través de DI.

0

Spring le ayuda a comprender e incluso fomenta los modelos DI. Sin embargo, no creo que tengas que tener Spring.

Puede tener archivos de configuración en Java que puede depurar, refactorizar y realizar análisis de código.

Le sugiero que ponga estos archivos de configuración java en otro paquete, pero son equivalentes a los archivos de configuración XML. Incluso puede cargar estos archivos dinámicamente si eso es importante.

6

Concedido, este es un ejemplo contruido, y con relaciones de objeto más complejas podría ser más eficiente almacenar un archivo XML que escribirlo programáticamente, pero seguramente debe haber más que eso?

creo que tiene más sentido para poner el "cableado" en una archivo de configuración en lugar de hacerlo manualmente en el código por varias razones:

  1. La configuración es externo a su código.
  2. Cambios en el cableado (para indicarle a su sawmill que use una instancia diferente de Saw) simplemente pueden hacerse al archivo externo (XML) y no requieren cambiar el código, volver a compilar, volver a implementar, etc.
  3. Cuando tiene docenas de clases y varias capas de inyección (por ejemplo: tiene una clase web Controller que obtiene una clase Service que contiene su lógica de negocio, que usa DAO para obtener Saw s de la base de datos, que obtiene DataSource inyectado en él, etc.), el cableado manual de los colaboradores es tedioso y requiere unas pocas docenas de líneas de código que no hacen más que cablear.
  4. Esto es algo menos que un "beneficio" claro, pero al tener todo el 'cableado' fuera del código, creo que ayuda a reforzar a los desarrolladores las ideas que están en el núcleo de la inyección de dependencia, específicamente la idea de codificar a la interfaz, no la implementación. Con el cableado manual arriba, puede ser fácil volver a deslizarse hacia atrás.
+0

Pensé que los primeros 3 quizás fueran menos claros que 4. :) Las configuraciones externas agregan direccionamiento indirecto (complejidad); mantener el código antiguo simple de Java puede ser más agradable que mantener las configuraciones de código y XML (aunque los IDEs modernos tienen un buen soporte para Spring); vuelve a la pregunta original ... – Jonik

+0

Ergo, la esencia de la inyección de dependencia es vencer al sistema de tipos? – Apocalisp

+0

Argumentaría que los archivos externos reducen la complejidad; en lugar de que su cableado suceda en todo el lugar, la información se almacena en un solo lugar; pero tal vez esa es solo mi opinión –

12

primavera tiene tres características que son igualmente importantes:

  1. inyección de dependencias
  2. la programación orientada a aspectos
  3. biblioteca de clases del framework para ayudar con la persistencia, la comunicación remota, mvc web, etc.

Estoy de acuerdo en que es difícil ver una ventaja en la inyección de dependencia cuando se compara con una sola llamada a n ew. En ese caso, este último ciertamente se verá más simple, porque es una sola línea de código. La configuración de Spring siempre aumentará las líneas de código, por lo que no es un argumento ganador.

Comienza a verse mucho mejor cuando se puede tomar una preocupación transversal como las transacciones fuera de sus clases y usar aspectos para configurarlas de manera declarativa. La comparación con una única llamada "nueva" no es para lo que se creó Spring.

Quizás el mejor resultado del uso de Spring sea la forma en que su idioma recomendado usa interfaces, capas y buenos principios como DRY. Realmente es solo la destilación de las mejores prácticas orientadas a objetos que Rod Johnson utilizó en sus conciertos de consultoría. Descubrió que el código que creó con el tiempo lo ayudó a ganar dinero ofreciendo mejores programas para sus clientes. Resumió su experiencia en "Experto 1: 1 J2EE" y terminó abriendo el código como Spring.

Yo diría comprar en el marco en la medida en que creas que su experiencia también puede ayudarte a escribir un mejor código.

No creo que pueda obtener el valor completo de Spring hasta que combine las tres características.

15

La inyección de dependencia es una forma degenerada de implicit parameter passing, y el propósito es esencialmente el mismo, para resolver lo que se llama The Configurations Problem:

El problema configuraciones es propagar las preferencias de tiempo de ejecución lo largo de un programa, lo que permite conjuntos de configuración simultánea múltiple para coexistir de forma segura bajo separación estática separación garantizada.

marcos de inyección de dependencias compensan la falta de implicit parameters, Curried functions y cómodas instalaciones para monads en el idioma.

1

Uno de los mayores beneficios de usar Dependency Injection es que facilita mucho más el envío de simulaciones o fragmentos de las dependencias de una clase al crear una prueba unitaria para esa clase. Esto le permite probar la clase de forma aislada sin depender de sus colaboradores.

En su ejemplo, no hay forma de simular o excluir Saw o SawMill en la clase donde se están creando instancias. Si Saw y SawMill se configuraron mediante setters o el constructor, entonces podría pasar su propia sierra simulada y burlarse de SawMill cuando ejecute la prueba unitaria.

+1

Es solo un beneficio SI no hay otra manera simple de lograr la separación entre clientes y colaboradores en pruebas unitarias. Pero el hecho es que existe una forma tan simple. Hay herramientas de burla que hacen precisamente eso. –

+0

De hecho, tienes razón Rogerio ...He estado utilizando recientemente su * excelente * framework JMockit y realmente nos está ayudando a escribir pruebas para nuestra base de código existente que no usó DI. ¡Gracias! –

3

No olvide una gran desventaja de la inyección de dependencia: pierde la capacidad de averiguar fácilmente desde dónde se inicializa algo utilizando Find Usages de su poderoso IDE de Java. Este podría ser un punto muy serio si refactoriza mucho y desea evitar que el código de prueba sea 10 veces más grande que el código de la aplicación.

+0

sí. Odio esto también, pero todos los demás patrones de uso indirecto (como "patrón de diseño de comando", auto-proxies ...) también sufren del mismo problema. –

2

Últimamente ha habido mucho énfasis en los marcos DI , así que el patrón de DI se olvida. principios de DI como summarized by J. B. Rainsberger:

Es muy sencillo: hacer explícita dependencias exigiendo colaboradores como parámetros en el constructor. Repita hasta que haya aplicado todas las decisiones sobre qué objetos crear en el punto de entrada. Por supuesto, esto solo se aplica a Servicios (en el sentido DDD). Hecho.

Como habrás notado, no hay mucha diferencia entre configurar manualmente las dependencias y utilizar un framework. Con la inyección del constructor, como se muestra a continuación, su ejemplo de código tendría incluso un texto secundario y el compilador lo obligaría a proporcionar todas las dependencias requeridas (y su IDE probablemente las escriba para usted).

marco
SawMill sawMill = new SawMill(new HandSaw()); 
sawMill.run(); 

Un DI puede reducir el texto modelo de creación de fábricas y el cableado de los objetos entre sí, pero al mismo tiempo, también puede hacer que sea más difícil de descubrir que en cada dependencia está viniendo - configuración del marco DI es una capa más de abstracción para excavar, y su IDE podría no poder decirle de dónde se llama un constructor particular.

Una desventaja indirecta de los marcos DI es que pueden hacer que el cableado de las dependencias sea demasiado fácil. Cuando ya no puede tener feel pain muchas dependencias, puede seguir agregando más dependencias en lugar de replantear el diseño de la aplicación para reducir el acoplamiento entre las clases. El cableado de las dependencias de forma manual, especialmente en el código de prueba, hace que sea más fácil darse cuenta cuando hay demasiadas dependencias a medida que la configuración de la prueba se hace más larga y se hace más difícil escribir las pruebas unitarias.

Algunas ventajas de los marcos DI provienen de su compatibilidad con funciones avanzadas como AOP (por ejemplo Spring's @Transactional), scoping (aunque muchas veces scoping with plain code serán suficientes) y pluggability (si realmente se necesita un plugin framework).

Recientemente realicé un experimento de DI manual frente a DI basado en framework. El progreso y los resultados se muestran como screencasts en Let's Code Dimdwarf episodes 42 through 47. El proyecto en cuestión tenía un sistema de complemento basado en Guice para crear actors, que luego reescribí usando DI manual, sin Guice. El resultado fue una implementación mucho más simple y clara, y solo un poco más repetitiva.

Sinopsis: Primero intente usar solo DI manual (preferiblemente inyección de constructor). Si se convierte en un montón de repetición, intente replantear el diseño para reducir las dependencias. Use un marco DI si algunas de sus características le proporcionan valor.