java - framework - Comprender la necesidad de un marco DI
spring framework (12)
Esta 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.
Considera 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");
}
}
Entonces podrías simplemente hacer:
Saw saw = new HandSaw();
SawMill sawMill = new SawMill();
sawMill.setSaw(saw);
sawMill.run();
Lo cual 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();
Por supuesto, este es un ejemplo ilustrado, y con relaciones de objeto más complejas, puede ser más eficiente almacenar un archivo XML que escribirlo programáticamente, pero seguramente debe haber más que eso.
(Sé que el marco Spring es más que eso, pero estoy pensando en la necesidad de un contenedor DI).
En el primer ejemplo, también sería trivial cambiar las dependencias midstream:
// gotta chop it faster
saw = new ChainSaw();
sawMill.setSaw(saw);
sawMill.run();
Por supuesto, este es un ejemplo ilustrado, y con relaciones de objeto más complejas, puede 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 colocar el "cableado" en un archivo de configuración en lugar de hacerlo manualmente en el código por varias razones:
- La configuración es externa a tu código.
- Los cambios en el cableado (para indicarle a su
sawmill
que use una instancia diferente deSaw
) simplemente pueden hacerse en el archivo externo (XML) y no requieren cambiar el código, volver a compilar, volver a implementar, etc. - Cuando tiene docenas de clases y varias capas de inyección (por ejemplo: tiene una clase de
Controller
web que obtiene una clase deService
que contiene su lógica empresarial, que usa unDAO
para obtenerSaw
de la base de datos, que obtiene unDataSource
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. - Esto es algo menos que un "beneficio" claro, pero al tener todo el ''cableado'' externo al 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 codificación a la interfaz, no a la implementación. Con el cableado manual arriba, puede ser fácil volver a deslizarse hacia atrás.
Es importante entender que Spring es fundamentalmente dos cosas, una construida sobre la otra:
- Un marco liviano DI / IoC y las clases para implementarlo (por ejemplo, contextos de aplicación XML, etc.); y
- 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 conectarlos entre sí.
Por lo general, no me importa la DI basada en XML o Reflection porque, en mis casos de uso, agrega 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 centralizo el acoplamiento y tengo todas las ventajas de eso, sin la dependencia de marcos DI más complejos o los archivos de configuración XML.
Si codifica la clase insertada, necesita que esa 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.
Spring te 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 coloque estos archivos de configuración de 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.
Spring tiene tres características que son igualmente importantes:
- inyección de dependencia
- Programación Orientada a Aspectos
- biblioteca de clases marco para ayudar con la persistencia, la comunicación remota, el 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 una nueva. 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 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 es la forma en que su idioma recomendado utiliza 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 pienses que su experiencia también puede ayudarte a escribir un mejor código.
No creo que pueda obtener el valor total de Spring hasta que combine las tres características.
Una cosa que la mayoría (si no todos) los contenedores DI / bibliotecas le traen además es la posibilidad de interceptar métodos para todas las instancias creadas a través de DI.
Uno de los mayores beneficios de usar Dependency Injection es que facilita mucho el suministro de simulaciones o resguardos 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 eliminar a 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.
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: ..." (llamémosle "clase A"). 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á", vale, así que ahora tengo mi vista acoplada a la conexión JDBC, donde solo debería tratar con HTML (o Swing, elija su sabor).
El marco DI, configurado por un XML (o JavaConfig) resuelve esto al permitirle simplemente "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 falsa con respecto al "más:" (donde lo hace SawMill springSawMill = (SawMill)context.getBean("sawMill"); springSawMill.run();
) - no necesita obtener el bean sawMill del contexto - el bean sawMill debería 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.
No olvide una desventaja importante de la inyección de dependencia: pierde la capacidad de descubrir fácilmente desde dónde se inicializa algo mediante Buscar usos 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.
Últimamente se ha puesto mucho énfasis en los marcos DI, incluso para que el patrón DI se esté olvidando. Los principios de DI resumidos por JB Rainsberger :
Es simple: hacer las dependencias explícitas requiriendo colaboradores como parámetros en el constructor. Repita hasta que haya empujado todas las decisiones sobre qué objetos crear en el punto de entrada. Por supuesto, esto solo se aplica a los Servicios (en el sentido DDD). Hecho.
Como habrás notado, no hay mucha diferencia entre configurar las dependencias manualmente versus usar 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).
SawMill sawMill = new SawMill(new HandSaw());
sawMill.run();
Un marco DI puede reducir la repetición de crear fábricas y cablear los objetos entre sí, pero al mismo tiempo también puede hacer que sea más difícil descubrir de dónde viene cada dependencia: la 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 sentir dolor por tener muchas dependencias, puede seguir agregando más dependencias en lugar de volver a pensar en el diseño de la aplicación para reducir el acoplamiento entre las clases. El cableado manual de las dependencias, especialmente en el código de prueba, hace que sea más fácil darse cuenta cuando tiene demasiadas dependencias a medida que la configuración de prueba se hace más larga y se hace más difícil escribir pruebas unitarias.
Algunas ventajas de los marcos DI provienen de su compatibilidad con características avanzadas como AOP (por ejemplo Spring''s @Transactional), scoping (aunque muchas veces es suficiente con un código simple ) y pluggability (si realmente se necesita un plugin framework).
Recientemente realicé un experimento de DI manual versus DI basado en framework. El progreso y los resultados se muestran como screencasts en Let''s Code Dimdwarf episodios 42 a 47 . El proyecto en cuestión tenía un sistema de complemento basado en Guice para crear actores , 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.
La inyección de dependencia es una forma degenerada de pasar parámetros implícitos , y el propósito es esencialmente el mismo, resolver lo que se llama el problema de configuraciones :
El problema de las configuraciones es propagar las preferencias de tiempo de ejecución a lo largo de un programa, lo que permite que varios conjuntos de configuración simultáneos coexistan de forma segura bajo separación estáticamente garantizada.
Los marcos de Inyección de Dependencia compensan la falta de parámetros implícitos , funciones Curried y facilidades convenientes para mónadas en el lenguaje.