unity ioc injection framework explicacion example dependency control c# dependency-injection inversion-of-control

c# - ioc - inversion of control vs dependency injection



Dependency Inject(DI) biblioteca "amigable" (4)

El término "inyección de dependencia" no tiene nada que ver específicamente con un contenedor IoC en absoluto, a pesar de que tienden a verlos mencionados juntos. Simplemente significa que en lugar de escribir su código como este:

public class Service { public Service() { } public void DoSomething() { SqlConnection connection = new SqlConnection("some connection string"); WindowsIdentity identity = WindowsIdentity.GetCurrent(); // Do something with connection and identity variables } }

Lo escribes así:

public class Service { public Service(IDbConnection connection, IIdentity identity) { this.Connection = connection; this.Identity = identity; } public void DoSomething() { // Do something with Connection and Identity properties } protected IDbConnection Connection { get; private set; } protected IIdentity Identity { get; private set; } }

Es decir, haces dos cosas cuando escribes tu código:

  1. Confíe en las interfaces en lugar de en las clases siempre que piense que la implementación podría necesitar un cambio;

  2. En lugar de crear instancias de estas interfaces dentro de una clase, pásalas como argumentos de constructor (alternativamente, podrían asignarse a propiedades públicas; la primera es inyección de constructor , la última es inyección de propiedad ).

Nada de esto presupone la existencia de ninguna biblioteca DI, y realmente no hace que el código sea más difícil de escribir sin una.

Si está buscando un ejemplo de esto, no busque más allá de .NET Framework:

  • List<T> implementa IList<T> . Si IEnumerable<T> tu clase para usar IList<T> (o IEnumerable<T> ), puedes aprovechar conceptos como la carga lenta, como Linq to SQL, Linq to Entities y NHibernate, todo a través del escenario, generalmente a través de la propiedad inyección. Algunas clases de framework realmente aceptan un IList<T> como un argumento de constructor, como BindingList<T> , que se usa para varias características de enlace de datos.

  • Linq to SQL y EF se crean completamente alrededor de IDbConnection y las interfaces relacionadas, que se pueden transmitir a través de los constructores públicos. Aunque no necesitas usarlos; los constructores predeterminados funcionan bien con una cadena de conexión en algún archivo de configuración.

  • Si alguna vez trabajas en los componentes de WinForms, INameCreationService con "servicios", como INameCreationService o IExtenderProviderService . Ni siquiera sabes realmente cuáles son las clases concretas. .NET en realidad tiene su propio contenedor IoC, IContainer , que se usa para esto, y la clase Component tiene un método GetService que es el localizador de servicios real. Por supuesto, nada le impide utilizar cualquiera o todas estas interfaces sin el IContainer o ese localizador en particular. Los servicios en sí mismos solo están acoplados libremente con el contenedor.

  • Los contratos en WCF se construyen completamente alrededor de interfaces. La clase de servicio concreta real suele ser referenciada por su nombre en un archivo de configuración, que es esencialmente DI. Muchas personas no se dan cuenta de esto, pero es completamente posible intercambiar este sistema de configuración con otro contenedor IoC. Quizás más interesante, los comportamientos de servicio son todas instancias de IServiceBehavior que pueden agregarse más adelante. Una vez más, puede conectar esto fácilmente a un contenedor IoC y hacer que elija los comportamientos relevantes, pero la función es completamente utilizable sin uno.

Y así sucesivamente y así sucesivamente. Encontrarás DI por todas partes en .NET, es solo que normalmente se hace tan perfectamente que ni siquiera lo piensas como DI.

Si desea diseñar su biblioteca habilitada para DI para una máxima facilidad de uso, entonces la mejor sugerencia es probablemente suministrar su propia implementación de IoC predeterminada utilizando un contenedor ligero. IContainer es una excelente opción para esto porque es parte de .NET Framework.

Estoy considerando el diseño de una biblioteca de C #, que tendrá varias funciones de alto nivel diferentes. Por supuesto, esas funciones de alto nivel se implementarán utilizando los principios de diseño de clase SOLID tanto como sea posible. Como tal, probablemente habrá clases destinadas a los consumidores a usar directamente de forma regular, y "clases de soporte" que son dependencias de esas clases más comunes de "usuario final".

La pregunta es, ¿cuál es la mejor manera de diseñar la biblioteca para que sea:

  • Agnóstico de DI: aunque agregar un "soporte" básico para una o dos de las bibliotecas de DI comunes (StructureMap, Ninject, etc.) parece razonable, quiero que los consumidores puedan usar la biblioteca con cualquier marco de DI.
  • No utilizable como DI: si un consumidor de la biblioteca no utiliza ningún DI, la biblioteca debería ser lo más fácil de usar posible, reduciendo la cantidad de trabajo que tiene que hacer un usuario para crear todas estas dependencias "sin importancia" solo para llegar a Las clases "reales" que quieren usar.

Mi idea actual es proporcionar algunos "módulos de registro DI" para las bibliotecas DI comunes (por ejemplo, un registro StructureMap, un módulo Ninject) y un conjunto o clases de Fábrica que no son DI y contienen el acoplamiento a esas pocas fábricas.

¿Pensamientos?


En realidad, esto es fácil de hacer una vez que entiendes que la DI se trata de patrones y principios , no de tecnología.

Para diseñar la API de forma independiente del contenedor DI, siga estos principios generales:

Programa para una interfaz, no una implementación

Este principio es en realidad una cita (aunque de memoria) de Design Patterns , pero siempre debe ser su objetivo real . DI es solo un medio para lograr ese fin .

Aplicar el principio de hollywood

El principio de Hollywood en términos de DI dice: No llames al contenedor DI, te llamará a ti .

Nunca solicite directamente una dependencia llamando a un contenedor desde su código. Pídelo implícitamente usando Inyección Constructor .

Usar Inyección Constructor

Cuando necesite una dependencia, solicítela de forma estática a través del constructor:

public class Service : IService { private readonly ISomeDependency dep; public Service(ISomeDependency dep) { if (dep == null) { throw new ArgumentNullException("dep"); } this.dep = dep; } public ISomeDependency Dependency { get { return this.dep; } } }

Observe cómo la clase Servicio garantiza sus invariantes. Una vez que se crea una instancia, se garantiza que la dependencia estará disponible debido a la combinación de la cláusula de la Guardia y la palabra clave de readonly .

Usa Abstract Factory si necesitas un objeto de corta duración.

Las dependencias inyectadas con la inyección del constructor tienden a ser de larga duración, pero a veces se necesita un objeto de corta duración, o construir la dependencia basándose en un valor conocido solo en tiempo de ejecución.

Vea this para más información.

Componer solo en el ultimo momento responsable

Mantener los objetos desacoplados hasta el final. Normalmente, puede esperar y cablear todo en el punto de entrada de la aplicación. Esto se llama la raíz de composición .

Más detalles aquí:

Simplificar usando una fachada

Si cree que la API resultante se vuelve demasiado compleja para los usuarios novatos, siempre puede proporcionar algunas clases de Facade que encapsulan combinaciones de dependencia comunes.

Para proporcionar una fachada flexible con un alto grado de capacidad de descubrimiento, podría considerar proporcionar constructores fluidos. Algo como esto:

public class MyFacade { private IMyDependency dep; public MyFacade() { this.dep = new DefaultDependency(); } public MyFacade WithDependency(IMyDependency dependency) { this.dep = dependency; return this; } public Foo CreateFoo() { return new Foo(this.dep); } }

Esto permitiría a un usuario crear un Foo predeterminado escribiendo

var foo = new MyFacade().CreateFoo();

Sin embargo, sería muy fácil descubrir que es posible proporcionar una dependencia personalizada, y usted podría escribir

var foo = new MyFacade().WithDependency(new CustomDependency()).CreateFoo();

Si imagina que la clase MyFacade encapsula muchas dependencias diferentes, espero que quede claro cómo proporcionaría los valores predeterminados adecuados al mismo tiempo que hace que la extensibilidad sea visible.

FWIW, mucho después de escribir esta respuesta, amplié los conceptos en este documento y escribí una publicación más larga en el blog sobre bibliotecas amigables con el DI y una publicación complementaria sobre los marcos amigables con el DI .


Lo que haría sería diseñar mi biblioteca de una manera independiente del contenedor DI para limitar la dependencia del contenedor tanto como sea posible. Esto permite intercambiar el contenedor DI por otro si es necesario.

Luego, exponga la capa sobre la lógica DI a los usuarios de la biblioteca para que puedan usar cualquier marco que elija a través de su interfaz. De esta manera, aún pueden usar la funcionalidad de DI que usted expuso y son libres de usar cualquier otro marco para sus propios fines.

Permitir que los usuarios de la biblioteca conecten su propio marco DI me parece un poco incorrecto, ya que aumenta dramáticamente la cantidad de mantenimiento. Esto también se convierte más en un entorno de plugin que en DI directa.


EDITAR 2015 : el tiempo ha pasado, me doy cuenta ahora que todo esto fue un gran error. Los contenedores de IoC son terribles y el DI es una forma muy mala de lidiar con los efectos secundarios. Efectivamente, todas las respuestas aquí (y la pregunta en sí) deben ser evitadas. Simplemente tenga en cuenta los efectos secundarios, sepárelos del código puro y todo lo demás caerá en su lugar o será irrelevante e innecesario.

La respuesta original sigue:

Tuve que enfrentar esta misma decisión al desarrollar SolrNet . Comencé con el objetivo de ser amigable con el DI y sin agnóstico, pero a medida que agregué más y más componentes internos, las fábricas internas rápidamente se volvieron inmanejables y la biblioteca resultante fue inflexible.

Terminé escribiendo mi propio contenedor IoC incrustado muy simple al mismo tiempo que proporcionaba una instalación de Windsor y un módulo Ninject . Integrar la biblioteca con otros contenedores es solo una cuestión de cablear correctamente los componentes, por lo que podría integrarlo fácilmente con Autofac, Unity, StructureMap, lo que sea.

La desventaja de esto es que perdí la capacidad de renovar el servicio. También tomé una dependencia de CommonServiceLocator que podría haber evitado (podría refactorizar en el futuro) para hacer que el contenedor incrustado sea más fácil de implementar.

Más detalles en esta entrada de blog .

MassTransit parece depender de algo similar. Tiene una interfaz IObjectBuilder que es en realidad el IServiceLocator de CommonServiceLocator con un par de métodos más, luego implementa esto para cada contenedor, es decir, NinjectObjectBuilder y un módulo / instalación regular, es decir, MassTransitModule . Luego depende de IObjectBuilder para crear una instancia de lo que necesita. Este es un enfoque válido, por supuesto, pero personalmente no me gusta mucho, ya que en realidad está pasando el contenedor demasiado, usándolo como un localizador de servicios.

MonoRail implementa su propio contenedor , que implementa el antiguo IServiceProvider . Este contenedor se utiliza en todo este marco a través de una interfaz que expone servicios conocidos . Para obtener el contenedor de concreto, tiene un localizador de proveedores de servicios incorporado . Las instalaciones de Windsor dirigen este localizador de proveedores de servicios a Windsor, por lo que es el proveedor de servicios seleccionado.

En pocas palabras: no hay una solución perfecta. Al igual que con cualquier decisión de diseño, este problema exige un equilibrio entre flexibilidad, facilidad de mantenimiento y comodidad.