Si bien la respuesta de Marcos es ideal para escenarios web, la falla llave con su aplicación para todas las arquitecturas (es decir, el cliente rico, es decir, WPF, WinForms, iOS, etc.) es la suposición de que todos los componentes necesarios para una operación pueden/deben crearse a la vez.
Para servidores web, esto tiene sentido ya que cada solicitud es extremadamente efímera y un controlador ASP.NET MVC se crea mediante el marco subyacente (sin código de usuario) para cada solicitud que entra. Así el controlador y todas sus dependencias puede ser fácilmente compuesto por un marco DI, y hay muy poco costo de mantenimiento para hacerlo. Tenga en cuenta que el marco web es responsable de administrar la vida útil del controlador y, a todos los efectos, la duración de todas sus dependencias (que el marco DI creará/inyectará para usted en la creación del controlador). Está muy bien que las dependencias vivan durante la duración de la solicitud y su código de usuario no necesita administrar el tiempo de vida de los componentes y subcomponentes en sí. También tenga en cuenta que los servidores web son apátridas en diferentes solicitudes (excepto en el estado de la sesión, pero eso es irrelevante para esta discusión) y que nunca tiene múltiples instancias de controlador/controlador infantil que necesitan vivir al mismo tiempo para atender una sola solicitud.
En las aplicaciones de cliente enriquecido, sin embargo, este no es el caso. Si utiliza una arquitectura MVC/MVVM (¡lo que debería hacer!) La sesión de un usuario es larga y los controladores crean subcontroladores/controladores hermanos a medida que el usuario navega por la aplicación (consulte la nota sobre MVVM en la parte inferior). La analogía con el mundo de la web es que cada entrada de usuario (clic de botón, operación realizada) en una aplicación de cliente enriquecido equivale a una solicitud que recibe el marco web. Sin embargo, la gran diferencia es que desea que los controladores en una aplicación de cliente enriquecido permanezcan vivos entre las operaciones (es muy posible que el usuario realice varias operaciones en la misma pantalla, que se rige por un controlador en particular) y que los subcontroladores obtengan creado y destruido mientras el usuario realiza diferentes acciones (piense en un control de pestañas que crea la pestaña de forma perezosa si el usuario navega hacia él, o una pieza de UI que solo necesita cargarse si el usuario realiza acciones particulares en una pantalla).
Ambas estas características significan que que es el código de usuario que necesita para gestionar el tiempo de vida de los controladores/sub-controladores, y que las dependencias de los controladores debe no todos los crearse por adelantado (es decir: Sub-controladores, ver-modelos , otros componentes de presentación, etc.). Si usa un marco DI para realizar estas responsabilidades, terminará con no solo mucho más código donde no pertenece (Ver: Constructor over-injection anti-pattern), sino que también tendrá que pasar un contenedor de dependencia en la mayor parte de su capa de presentación, de modo que sus componentes puedan usarlo para crear sus subcomponentes cuando sea necesario.
¿Por qué es malo que mi código de usuario tenga acceso al contenedor DI?
1) El contenedor de dependencias contiene referencias a una gran cantidad de componentes en su aplicación. Pasar este chico malo a cada componente que necesita crear/administrar un subcomponente anoter es el equivalente a usar elementos globales en su arquitectura. Peor aún, cualquier subcomponente también puede registrar nuevos componentes en el contenedor, tan pronto como sea posible se convertirá en un almacenamiento global. Los desarrolladores lanzarán objetos al contenedor solo para pasar los datos entre los componentes (ya sea entre los controladores hermanos o entre las jerarquías de los controladores profundos, es decir, un controlador ancestro necesita tomar datos de un controlador de abuelos). Tenga en cuenta que en el mundo web donde el contenedor no se pasa al código de usuario, esto nunca es un problema.
2) El otro problema con los contenedores de dependencia frente a los localizadores/fábricas de servicios/instanciación directa de objetos es que resolver desde un contenedor hace que sea completamente ambiguo si está CREANDO un componente o simplemente REUSANDO uno existente. En cambio, se deja a una configuración centralizada (es decir, bootstrapper/Composition Root) para averiguar cuál es la duración del componente. En ciertos casos, esto está bien (es decir, controladores web, donde no es el código de usuario el que necesita administrar la duración del componente, sino el propio marco de procesamiento de solicitud de tiempo de ejecución). Sin embargo, esto es extremadamente problemático cuando el diseño de los componentes debe INDICAR si es su responsabilidad administrar un componente y cuál debe ser su duración (Ejemplo: una aplicación de teléfono muestra una hoja que le pide al usuario cierta información. Esto se logra mediante un El controlador crea un subcontrolador que gobierna la hoja superpuesta. Una vez que el usuario ingresa cierta información, la hoja se renueva y el control se devuelve al controlador inicial, que todavía mantiene el estado de lo que el usuario estaba haciendo previamente. Si DI se usa para resolver el subcontrolador de hojas, es ambiguo qué duración debería tener o quién debería ser el responsable de gestionarlo (el controlador iniciador). Compare esto con la responsabilidad explícita dictada por el uso de otros mecanismos.
Escenario A:
// not sure whether I'm responsible for creating the thing or not
DependencyContainer.GimmeA<Thing>()
Escenario B:
// responsibility is clear that this component is responsible for creation
Factory.CreateMeA<Thing>()
// or simply
new Thing()
Escenario C:
// responsibility is clear that this component is not responsible for creation, but rather only consumption
ServiceLocator.GetMeTheExisting<Thing>()
// or simply
ServiceLocator.Thing
Como se puede ver DI hace que sea poco claro quien es responsable de la administración de la duración de el subcomponente
NOTA: Técnicamente hablando muchos marcos de DI tienen alguna forma de crear componentes con pereza (Ver: How not to do dependency injection - the static or singleton container), que es mucho mejor de lo que pasa el recipiente alrededor, pero aún se está pagando el costo de mutando su código para pasar funciones de creación en todas partes, falta soporte de primer nivel para pasar parámetros de constructor válidos durante la creación, y al final del día todavía está usando un mecanismo de indirección innecesariamente en lugares donde el único beneficio es para lograr la capacidad de prueba, que se puede lograr de maneras mejores y más simples (ver a continuación).
¿Qué significa todo esto?
Significa que DI es apropiado para ciertos escenarios e inapropiado para otros. En las aplicaciones de clientes ricos, sucede que tiene muchas de las desventajas de la DI con muy pocas ventajas. Mientras más grande sea su complejidad en la aplicación, mayores serán los costos de mantenimiento. También conlleva el grave potencial de uso indebido, que de acuerdo con cuán ajustados sean los procesos de comunicación de su equipo y de revisión de códigos, puede ser cualquier cosa, desde un costo de emisión de la tecnología no grave hasta un costo severo. Existe el mito de que los Localizadores o Fábricas de Servicios o las buenas instancias antiguas son de alguna manera mecanismos malos y desactualizados simplemente porque pueden no ser el mecanismo óptimo en el mundo de las aplicaciones web, donde tal vez mucha gente juegue. No deberíamos exagerar. generalice estos aprendizajes a todos los escenarios y vea todo como uñas solo porque hemos aprendido a manejar un martillo en particular.
Mi recomendación PARA APLICACIONES DE CLIENTE RICO es utilizar el mecanismo mínimo que cumpla con los requisitos para cada componente a mano. El 80% del tiempo esto debería ser una instanciación directa. Los localizadores de servicios se pueden usar para alojar los principales componentes de su capa empresarial (es decir, servicios de aplicaciones que generalmente son de naturaleza singleton) y, por supuesto, las fábricas e incluso el patrón Singleton también tienen su lugar. No hay nada que decir que no pueda usar un marco DI oculto detrás de su localizador de servicios para crear sus dependencias de capa empresarial y todo lo que dependen de una vez, si eso le facilita la vida en esa capa, y esa capa no lo hace No exhibe la carga diferida que las capas de presentación de cliente rico abrumadoramente hacen. Solo asegúrate de proteger tu código de usuario del acceso a ese contenedor para que puedas evitar el desorden que puede ocasionar al pasar un contenedor DI.
¿Qué hay de la capacidad de prueba?
La capacidad de prueba se puede lograr absolutamente sin un marco DI. Recomiendo usar un marco de interceptación como UnitBox (gratis) o TypeMock (caro). Estos frameworks le brindan las herramientas que necesita para resolver el problema (cómo se burla de la creación de instancias y las llamadas estáticas en C#) y no le piden que cambie toda su arquitectura para evitarlos (que desafortunadamente es donde la tendencia ha ido en el mundo .NET/Java). Es más sensato encontrar una solución al problema en cuestión y utilizar los mecanismos y patrones de lenguaje natural óptimos para el componente subyacente, luego intentar ajustar cada clavija cuadrada en el agujero DI redondo. Una vez que empiece a utilizar estos mecanismos más simples y más específicos, notará que hay muy poca necesidad de DI en su base de códigos, si es que hay alguno.
NOTA: Para las arquitecturas MVVM
En las arquitecturas básicas MVVM vista-modelos toman de manera efectiva en la responsabilidad de los controladores, lo que para fines consideran el redacción 'controlador' de arriba para aplicarse a 'vista -modelo'. MVVM básico funciona bien para aplicaciones pequeñas, pero a medida que la complejidad de una aplicación crece, puede que desee para utilizar un enfoque MVCVM. Los modelos de vista se vuelven DTO en su mayoría tontos a facilitar la vinculación de datos a la vista mientras interacción con la capa empresarial y entre grupos de modelos de vista que representan pantallas/subpantallas se encapsula en componentes controladores/subcontroladores explícitos. En cualquiera de las arquitecturas, existe la responsabilidad de los controladores y muestra las mismas características de discutidas anteriormente.
Una pregunta con respecto a su último párrafo. ¿Tiene algún enlace a ejemplos de este tipo de diseño? –
Eso sería simplemente DI básica como Constructor Injection y tal ... –
Re último párrafo- ¿significa esto que no usa el IOC en las pruebas? Solo aclarando. Ah, pero ¿y si el código que estás probando lo necesita para construirse? – Greg