Estoy buscando implementar la inyección de dependencia en una aplicación relativamente grande, pero no tengo experiencia en ello. Estudié el concepto y algunas implementaciones de IoC e inyectores de dependencia disponibles, como Unity y Ninject. Sin embargo, hay una cosa que me está eludiendo. ¿Cómo debo organizar la creación de instancias en mi aplicación?
Lo que estoy pensando es que puedo crear algunas fábricas específicas que contendrán la lógica de crear objetos para algunos tipos de clase específicos. Básicamente, una clase estática con un método que invoca el método Ninject Get () de una instancia de kernel estático en esta clase.
¿Será un enfoque correcto para implementar la inyección de dependencia en mi aplicación o debería implementarlo de acuerdo con algún otro principio?
fuente
Respuestas:
No pienses aún en la herramienta que vas a utilizar. Puede hacer DI sin un contenedor de IoC.
Primer punto: Mark Seemann tiene un muy buen libro sobre DI en .Net
Segundo: composición raíz. Asegúrese de que toda la configuración se realice en el punto de entrada del proyecto. El resto de su código debe saber sobre las inyecciones, no sobre cualquier herramienta que se esté utilizando.
Tercero: la inyección de constructor es el camino más probable (hay casos en los que no lo querría, pero no tantos).
Cuarto: considere el uso de fábricas lambda y otras características similares para evitar crear interfaces / clases innecesarias con el único propósito de inyección.
fuente
Su pregunta tiene dos partes: cómo implementar DI correctamente y cómo refactorizar una aplicación grande para usar DI.
La primera parte es bien respondida por @Miyamoto Akira (especialmente la recomendación de leer el libro "inyección de dependencia en .net" de Mark Seemann. El blog de Marcas también es un buen recurso gratuito.
La segunda parte es mucho más complicada.
Un buen primer paso sería simplemente mover todas las instancias a los constructores de clases, sin inyectar las dependencias, solo asegurándose de que solo llame
new
al constructor.Esto resaltará todas las violaciones de SRP que ha estado cometiendo, para que pueda comenzar a dividir la clase en colaboradores más pequeños.
El próximo problema que encontrará serán las clases que dependen de parámetros de tiempo de ejecución para la construcción. Por lo general, puede solucionar esto creando fábricas simples, a menudo con
Func<param,type>
, inicializándolas en el constructor y llamándolas en los métodos.El siguiente paso sería crear interfaces para sus dependencias y agregar un segundo constructor a sus clases que excepto estas interfaces. Su constructor sin parámetros renovaría las instancias concretas y las pasaría al nuevo constructor. Esto se conoce comúnmente como 'Inyección B * stard' o 'Pobre mans DI'.
Esto le dará la capacidad de hacer algunas pruebas unitarias, y si ese era el objetivo principal del refactor, puede ser donde se detenga. El nuevo código se escribirá con la inyección del constructor, pero su código antiguo puede continuar funcionando como está escrito pero aún así se puede probar.
Por supuesto, puedes ir más allá. Si tiene la intención de usar un contenedor IOC, entonces el siguiente paso podría ser reemplazar todas las llamadas directas
new
en sus constructores sin parámetros con llamadas estáticas al contenedor IOC, esencialmente (ab) usándolo como un localizador de servicios.Esto arrojará más casos de parámetros de constructor de tiempo de ejecución para tratar como antes.
Una vez hecho esto, puede comenzar a eliminar los constructores sin parámetros y refactorizar a DI puro.
En última instancia, esto va a ser mucho trabajo, así que asegúrese de decidir por qué quiere hacerlo y priorice las partes de la base de código que se beneficiarán más del refactorizador.
fuente
Primero, quiero mencionar que está haciendo esto mucho más difícil para usted al refactorizar un proyecto existente en lugar de comenzar un nuevo proyecto.
Dijiste que es una aplicación grande, así que elige un componente pequeño para comenzar. Preferiblemente un componente 'nodo de hoja' que no es usado por nada más. No sé cuál es el estado de las pruebas automatizadas en esta aplicación, pero interrumpirá todas las pruebas unitarias para este componente. Así que esté preparado para eso. El paso 0 es escribir pruebas de integración para el componente que modificará si aún no existen. Como último recurso (sin infraestructura de prueba; sin aceptación para escribirlo), descubra una serie de pruebas manuales que puede hacer para verificar que este componente esté funcionando.
La forma más sencilla de establecer su objetivo para el refactor DI es que desea eliminar todas las instancias del operador 'nuevo' de este componente. Estos generalmente se dividen en dos categorías:
Variable miembro invariable: son variables que se establecen una vez (generalmente en el constructor) y no se reasignan durante la vida útil del objeto. Para estos, puede inyectar una instancia del objeto en el constructor. Por lo general, no es responsable de deshacerse de estos objetos (no quiero decir que nunca aquí, pero realmente no debería tener esa responsabilidad).
Variable miembro variable / variable de método: Estas son variables que obtendrán basura recolectada en algún momento durante la vida útil del objeto. Para estos, querrá inyectar una fábrica en su clase para proporcionar estas instancias. Usted es responsable de eliminar los objetos creados por una fábrica.
Su contenedor IoC (ninject suena como) se encargará de crear instancias de esos objetos e implementar sus interfaces de fábrica. Lo que sea que esté usando el componente que ha modificado necesitará saber sobre el contenedor IoC para que pueda recuperar su componente.
Una vez que haya completado lo anterior, podrá obtener los beneficios que espera obtener de DI en su componente seleccionado. Ahora sería un buen momento para agregar / corregir esas pruebas unitarias. Si hubiera pruebas unitarias existentes, tendrá que tomar una decisión sobre si desea unirlas mediante la inyección de objetos reales o escribir nuevas pruebas unitarias utilizando simulacros.
'Simplemente' repita lo anterior para cada componente de su aplicación, moviendo la referencia al contenedor de IoC hacia arriba a medida que avanza hasta que solo sea necesario saberlo.
fuente
El enfoque correcto es usar inyección de constructor, si usa
entonces terminas con el localizador de servicios, que la inyección de dependencia.
fuente
Dices que quieres usarlo pero no dices por qué.
DI no es más que proporcionar un mecanismo para generar concreciones a partir de interfaces.
Esto en sí mismo proviene del DIP . Si su código ya está escrito en este estilo y tiene un solo lugar donde se generan concreciones, DI no trae nada más a la fiesta. Agregar código de marco DI aquí simplemente hincharía y ofuscaría su base de código.
Asumiendo que no desee utilizarlo, normalmente se configura la fábrica / constructor / contenedor (o lo que sea) al comienzo de la aplicación por lo que es claramente visible.
Nota: es muy fácil rodar el suyo si lo desea, en lugar de comprometerse con Ninject / StructureMap o lo que sea. Sin embargo, si tiene una rotación de personal razonable, puede engrasar las ruedas para usar un marco reconocido o al menos escribirlo con ese estilo para que no sea una curva de aprendizaje demasiado.
fuente
En realidad, la forma "correcta" es NO usar una fábrica en absoluto a menos que no haya absolutamente otra opción (como en las pruebas unitarias y ciertos simulacros, ¡para el código de producción NO se usa una fábrica)! Hacerlo es en realidad un antipatrón y debe evitarse a toda costa. El objetivo detrás de un contenedor DI es permitir que el dispositivo haga el trabajo por usted.
Como se indicó anteriormente en una publicación anterior, desea que su dispositivo IoC asuma la responsabilidad de la creación de los diversos objetos dependientes en su aplicación. Eso significa permitir que su dispositivo DI cree y administre las distintas instancias. Este es todo el punto detrás de DI: sus objetos NUNCA deben saber cómo crear y / o administrar los objetos de los que dependen. Hacer lo contrario rompe el acoplamiento flojo.
La conversión de una aplicación existente a todos los DI es un gran paso, pero dejando de lado las dificultades obvias para hacerlo, también querrá (solo para hacer su vida un poco más fácil) explorar una herramienta DI que realizará la mayor parte de sus enlaces automáticamente (el núcleo de algo como Ninject son las
"kernel.Bind<someInterface>().To<someConcreteClass>()"
llamadas que realiza para hacer coincidir sus declaraciones de interfaz con las clases concretas que desea utilizar para implementar esas interfaces. Son esas llamadas "Bind" las que permiten que su dispositivo DI intercepte sus llamadas de constructor y proporcione instancias de objeto dependiente necesarias Un constructor típico (pseudocódigo mostrado aquí) para alguna clase podría ser:Tenga en cuenta que en ningún lugar de ese código había ningún código que creara / administrara / liberara la instancia de SomeConcreteClassA o SomeOtherConcreteClassB. De hecho, ninguna clase concreta fue referenciada. Entonces ... ¿dónde sucedió la magia?
En la parte de inicio de su aplicación, ocurrió lo siguiente (de nuevo, este es un pseudocódigo pero está bastante cerca de lo real (Ninject) ...):
Ese pequeño fragmento de código le dice al dispositivo Ninject que busque constructores, los escanee, busque instancias de interfaces que haya sido configurado para manejar (esas son las llamadas "Bind") y luego cree y sustituya una instancia de la clase concreta donde sea Se hace referencia a la instancia.
Hay una buena herramienta que complementa muy bien a Ninject llamada Ninject.Extensions.Conventions (otro paquete NuGet) que hará la mayor parte de este trabajo por usted. No para alejarse de la excelente experiencia de aprendizaje que atravesará a medida que lo desarrolle usted mismo, pero para comenzar, esta podría ser una herramienta para investigar.
Si la memoria funciona, Unity (formalmente de Microsoft ahora un proyecto de código abierto) tiene una llamada al método o dos que hacen lo mismo, otras herramientas tienen ayudantes similares.
Sea cual sea el camino que elija, definitivamente lea el libro de Mark Seemann para la mayor parte de su formación DI, sin embargo, debe señalarse que incluso los "Grandes" del mundo de la ingeniería de software (como Mark) pueden cometer errores evidentes: Mark se olvidó por completo de Ninject en su libro, así que aquí hay otro recurso escrito solo para Ninject. Lo tengo y es una buena lectura: Dominar Ninject para inyección de dependencia
fuente
No existe un "camino correcto", pero hay algunos principios simples a seguir:
Eso es todo. Por supuesto, esos son principios, no leyes, pero si los sigue, puede estar seguro de que hace DI (corríjame si me equivoco).
Entonces, ¿cómo crear objetos durante el tiempo de ejecución sin "nuevo" y sin conocer el contenedor DI?
En el caso de NInject, hay una extensión de fábrica que proporciona la creación de fábricas. Claro, las fábricas creadas todavía tienen una referencia interna al núcleo, pero eso no es accesible desde su aplicación.
fuente