c# inyeccion - ¿Cómo evitar la dependencia del constructor de inyección de dependencia?




dependencias unity (9)

Encuentro que mis constructores están empezando a verse así:

public MyClass(Container con, SomeClass1 obj1, SomeClass2, obj2.... )

con cada vez mayor lista de parámetros. Ya que "Container" es mi contenedor de inyección de dependencia, ¿por qué no puedo hacer esto?

public MyClass(Container con)

para cada clase? ¿Cuáles son las desventajas? Si hago esto, siento que estoy usando una estática glorificada. Por favor comparta sus pensamientos sobre la locura de inyección de dependencia y la IoC.


Answers

No creo que los constructores de su clase deban tener una referencia al período de contenedor de IOC. Esto representa una dependencia innecesaria entre su clase y el contenedor (¡el tipo de dependencia que el IOC está tratando de evitar!).


Problema

1) Constructor con cada vez mayor lista de parámetros.

2) Si la clase se hereda (Ej: RepositoryBase ), cambiar la firma del constructor causa un cambio en las clases derivadas.

Solución 1

Pasar el IoC Container al constructor

Por qué

  • No más lista de parámetros cada vez mayor
  • La firma del constructor se vuelve simple

Por qué no

  • Hace que la clase esté bien acoplada al contenedor IoC. (Eso causa problemas cuando 1. quiere usar esa clase en otros proyectos donde usa un contenedor IoC diferente. 2. decide cambiar el contenedor IoC)
  • Te hace la clase menos descriptiva. (No se puede mirar al constructor de la clase y decir lo que necesita para funcionar).
  • La clase puede acceder potencialmente a todos los servicios.

Solucion 2

Crear una clase que agrupe todos los servicios y pasarlo al constructor.

 public abstract class EFRepositoryBase 
 {
    public class Dependency
    {
        public DbContext DbContext { get; }
        public IAuditFactory AuditFactory { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
        }
    }

    protected readonly DbContext DbContext;        
    protected readonly IJobariaAuditFactory auditFactory;

    protected EFRepositoryBase(Dependency dependency)
    {
        DbContext = dependency.DbContext;
        auditFactory= dependency.JobariaAuditFactory;
    }
  }

Clase derivada

  public class ApplicationEfRepository : EFRepositoryBase      
  {
     public new class Dependency : EFRepositoryBase.Dependency
     {
         public IConcreteDependency ConcreteDependency { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory,
            IConcreteDependency concreteDependency)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
            ConcreteDependency = concreteDependency;
        }
     }

      IConcreteDependency _concreteDependency;

      public ApplicationEfRepository(
          Dependency dependency)
          : base(dependency)
      { 
        _concreteDependency = dependency.ConcreteDependency;
      }
   }

Por qué

  • Agregar una nueva dependencia a la clase no afecta a las clases derivadas
  • La clase es agnóstica de IoC Container
  • La clase es descriptiva (en aspecto de sus dependencias). Por convención, si desea saber de qué clase depende, esa información se acumula en A.Dependency
  • La firma del constructor se vuelve simple

Por qué no

  • Necesito crear clase adicional
  • el registro del servicio se vuelve complejo (es necesario registrar cada X.Dependency separado)
  • Conceptualmente lo mismo que pasar el IoC Container
  • ..

Sin embargo, la Solución 2 es solo un crudo, si hay un argumento sólido en su contra, entonces se agradecería un comentario descriptivo


¿Qué marco de inyección de dependencia está utilizando? ¿Ha intentado utilizar la inyección basada en incubadora en su lugar?

El beneficio de la inyección basada en el constructor es que parece natural para los programadores de Java que no usan frameworks DI. Necesita 5 cosas para inicializar una clase, entonces tiene 5 argumentos para su constructor. El inconveniente es lo que ha notado, se vuelve difícil de manejar cuando tiene muchas dependencias.

Con Spring, podría pasar los valores requeridos con los configuradores en su lugar y podría usar las anotaciones necesarias para imponer que se inyecten. El inconveniente es que necesita mover el código de inicialización del constructor a otro método y hacer que Spring llame después de inyectar todas las dependencias al marcarlo con @PostConstruct. No estoy seguro de otros marcos, pero supongo que hacen algo similar.

Ambas formas funcionan, es una cuestión de preferencia.


Este es el enfoque que utilizo

public class Hero
{

    [Inject]
    private IInventory Inventory { get; set; }

    [Inject]
    private IArmour Armour { get; set; }

    [Inject]
    protected IWeapon Weapon { get; set; }

    [Inject]
    private IAction Jump { get; set; }

    [Inject]
    private IInstanceProvider InstanceProvider { get; set; }


}

Aquí hay un enfoque básico sobre cómo realizar las inyecciones y ejecutar el constructor después de inyectar los valores. Este es un programa totalmente funcional.

public class InjectAttribute : Attribute
{

}


public class TestClass
{
    [Inject]
    private SomeDependency sd { get; set; }

    public TestClass()
    {
        Console.WriteLine("ctor");
        Console.WriteLine(sd);
    }
}

public class SomeDependency
{

}


class Program
{
    static void Main(string[] args)
    {
        object tc = FormatterServices.GetUninitializedObject(typeof(TestClass));

        // Get all properties with inject tag
        List<PropertyInfo> pi = typeof(TestClass)
            .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
            .Where(info => info.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0).ToList();

        // We now happen to know there's only one dependency so we take a shortcut just for the sake of this example and just set value to it without inspecting it
        pi[0].SetValue(tc, new SomeDependency(), null);


        // Find the right constructor and Invoke it. 
        ConstructorInfo ci = typeof(TestClass).GetConstructors()[0];
        ci.Invoke(tc, null);

    }
}

Actualmente estoy trabajando en un proyecto de hobby que funciona así https://github.com/Jokine/ToolProject/tree/Core


Leí todo este hilo dos veces y creo que las personas responden por lo que saben, no por lo que se les pregunta.

La pregunta original de JP parece que está construyendo objetos mediante el envío de un programa de resolución, y luego un montón de clases, pero suponemos que esas clases / objetos son en sí mismos servicios, listos para ser inyectados. ¿Y si no lo son?

JP, si está buscando aprovechar el DI y desear la gloria de mezclar la inyección con datos contextuales, ninguno de estos patrones (o supuestos "anti-patrones") aborda eso específicamente. En realidad se reduce a usar un paquete que lo apoyará en tal esfuerzo.

Container.GetSevice<MyClass>(someObject1, someObject2)

... este formato rara vez es compatible. Creo que la dificultad de programar dicho soporte, sumado al rendimiento miserable que se asociaría con la implementación, lo hace poco atractivo para los desarrolladores de código abierto.

Pero debería hacerse, porque debería poder crear y registrar una fábrica para MyClass'es, y esa fábrica debería poder recibir datos / entradas que no se convierten en un "servicio" solo por el simple hecho de pasar. datos. Si "antipatrón" tiene que ver con consecuencias negativas, entonces forzar la existencia de tipos de servicios artificiales para pasar datos / modelos es ciertamente negativo (a la par con su sentimiento de envolver sus clases en un contenedor. Se aplica el mismo instinto).

Sin embargo, hay un marco que puede ayudar, incluso si se ven un poco feos. Por ejemplo, Ninject:

Creando una instancia usando Ninject con parámetros adicionales en el constructor

Eso es para .NET, es popular y todavía no está tan limpio como debería, pero estoy seguro de que hay algo en cualquier idioma que elijas emplear.


La dificultad de pasar en los parámetros no es el problema. El problema es que tu clase está haciendo demasiado, y debería dividirse más.

La inyección de dependencia puede actuar como una advertencia temprana para las clases que se hacen demasiado grandes, especialmente debido al dolor creciente de pasar en todas las dependencias.


Me encontré con una pregunta similar acerca de la Inyección de dependencia basada en el constructor y lo complejo que era pasar en todas las dependencias.

Uno de los enfoques que he usado en el pasado es usar el patrón de fachada de la aplicación usando una capa de servicio. Esto tendría una API gruesa. Si este servicio depende de los repositorios, usaría una inyección de establecimiento de las propiedades privadas. Esto requiere crear una fábrica abstracta y mover la lógica de crear los repositorios a una fábrica.

Código detallado con explicación se puede encontrar aquí

Mejores prácticas para IoC en la capa de servicio complejo


Tiene razón en que si utiliza el contenedor como Localizador de servicios, es más o menos una fábrica estática glorificada. Por muchas razones considero que esto es un anti-patrón .

Uno de los maravillosos beneficios de Constructor Injection es que hace que las violaciones del Principio de Responsabilidad Única sean evidentes.

Cuando eso sucede, es hora de refactorizar los servicios de fachada . En resumen, cree una nueva interfaz de grano más grueso que oculte la interacción entre algunas o todas las dependencias de grano fino que necesita actualmente.


Recientemente me encontré con una situación en la que tenía varias dependencias en una clase, pero solo una de las dependencias iba a cambiar necesariamente en cada implementación. Dado que las dependencias de acceso a datos y registro de errores probablemente solo se cambiarían para fines de prueba, agregué parámetros opcionales para esas dependencias y proporcioné implementaciones predeterminadas de esas dependencias en mi código de constructor. De esta manera, la clase mantiene su comportamiento predeterminado a menos que sea anulado por el consumidor de la clase.

El uso de parámetros opcionales solo se puede lograr en marcos que los admitan, como .NET 4 (tanto para C # como para VB.NET, aunque VB.NET siempre los ha tenido). Por supuesto, puede lograr una funcionalidad similar simplemente usando una propiedad que pueda ser reasignada por el consumidor de su clase, pero no obtiene la ventaja de la inmutabilidad proporcionada al tener un objeto de interfaz privado asignado a un parámetro del constructor.

Dicho todo esto, si está presentando una nueva dependencia que debe ser proporcionada por cada consumidor, tendrá que refactorizar su constructor y todo el código que los consumidores de su clase. Mis sugerencias anteriores solo se aplican si tiene el lujo de poder proporcionar una implementación predeterminada para todo su código actual, pero aún así puede anular la implementación predeterminada si es necesario.





c# java dependency-injection inversion-of-control ioc-container