¿Cómo evitar la locura del constructor de inyección de dependencia?

Encuentro que mis constructores comienzan a verse así:

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

con una lista de parámetros cada vez mayor. Dado 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, se siente como si estuviera usando una estática glorificada. Comparta sus pensamientos sobre la locura de IoC y la inyección de dependencia.

preguntado el 10 de marzo de 10 a las 17:03

¿Por qué pasas el contenedor? Creo que puede malinterpretar el COI.

Si sus constructores exigen más o más parámetros, es posible que esté haciendo demasiado en esas clases. -

No es así como se hace la inyección de constructores. Los objetos no conocen el contenedor de IoC en absoluto, ni deberían hacerlo. -

Puede simplemente crear un constructor vacío, en el que llame directamente a la DI pidiendo las cosas que necesita. Eso eliminará el constructor madnes, pero debe asegurarse de que está utilizando una interfaz DI ... en caso de que cambie su sistema DI a la mitad del desarrollo. Honestamente ... nadie retrocederá haciéndolo de esta manera, aunque esto es lo que hace DI para inyectar en su constructor. doh -

9 Respuestas

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

Uno de los maravillosos beneficios de Constructor Injection es que infringe las Principio de responsabilidad única deslumbrantemente obvio.

Cuando eso suceda, es hora de refactorizar a Facade Services. En resumen, cree una nueva, más de grano grueso interfaz que oculta la interacción entre algunas o todas las dependencias detalladas que necesita actualmente.

Respondido 06 Oct 16, 06:10

+1 para cuantificar el esfuerzo de refactorización en un solo concepto; increíble :) - ryan emerle

¿verdadero? acaba de crear una indirección para mover esos parámetros a otra clase, ¡pero todavía están allí! simplemente más complicado lidiar con ellos. - irrefutable

@reputable: En el caso degenerado donde nos movemos todos los dependencias en un servicio agregado Estoy de acuerdo en que es solo otro nivel de indirección que no conlleva ningún beneficio, por lo que mi elección de palabras fue ligeramente diferente. Sin embargo, el punto es que solo nos movemos algo de las dependencias detalladas en un Servicio agregado. Esto limita el número de permutaciones de dependencia tanto en el nuevo Servicio agregado como para las dependencias que quedan. Esto hace que sea más sencillo tratar con ambos. - marca seemann

El mejor comentario: "Uno de los maravillosos beneficios de Constructor Injection es que hace que las violaciones del Principio de Responsabilidad Única sean claramente obvias". - Ígor Popov

@DonBox En ese caso, puede escribir implementaciones de objetos nulos para detener la recursividad. No es lo que necesita, pero el punto es que Constructor Injection no evita los ciclos, solo deja en claro que están ahí. - marca seemann

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

respondido 10 mar '10, 20:03

+1 Depender de un contenedor de IoC hace que sea difícil cambiar ese contenedor más adelante sin cambiar el conjunto de código en todas las demás clases - Tseng

¿Cómo implementaría IOC sin tener parámetros de interfaz en los constructores? ¿Estoy leyendo mal tu publicación? - j caza

@J Hunt No entiendo tu comentario. Para mí, los parámetros de interfaz significan parámetros que son interfaces para las dependencias, es decir, si su contenedor de inyección de dependencia se inicializa MyClass myClass = new MyClass(IDependency1 interface1, IDependency2 interface2) (parámetros de interfaz). Esto no está relacionado con la publicación de @ derivación, que interpreto como que dice que un contenedor de inyección de dependencia no debería inyectarse en sus objetos, es decir, MyClass myClass = new MyClass(this) - Fulano de Tal

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

La inyección de dependencia puede actuar como una advertencia temprana para las clases que se vuelven demasiado grandes, específicamente debido al dolor cada vez mayor de pasar todas las dependencias.

respondido 11 mar '10, 05:03

Corrígeme si me equivoco, pero en algún momento tienes que 'pegar todo' y, por lo tanto, tienes que obtener más de unas pocas dependencias para eso. Por ejemplo, en la capa Ver, al crear plantillas y datos para ellos, debe tomar todos los datos de varias dependencias (por ejemplo, 'servicios') y luego poner todos estos datos en la plantilla y en la pantalla. Si mi página web tiene 10 'bloques' diferentes de información, entonces necesito 10 clases diferentes para proporcionarme esos datos. ¿Entonces necesito 10 dependencias en mi clase de Vista / Plantilla? - Andrés

Me encontré con una pregunta similar sobre la inyección de dependencia basada en constructores y lo complejo que se estaba volviendo para pasar 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 burda. Si este servicio depende de repositorios, usaría una inyección de setter de las propiedades privadas. Esto requiere crear una fábrica abstracta y trasladar la lógica de crear los repositorios a una fábrica.

El código detallado con una explicación se puede encontrar aquí.

Mejores prácticas para IoC en capas de servicios complejas

contestado el 23 de mayo de 17 a las 11:05

Problema:

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

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

solución 1

Pasar 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 su clase esté estrechamente acoplada al contenedor IoC. (Eso causa problemas cuando 1. desea usar esa clase en otros proyectos en los que usa un contenedor de IoC diferente. 2. decide cambiar el contenedor de IoC)
  • Hace que tu clase sea menos descriptiva. (Realmente no puede mirar el constructor de clases y decir lo que necesita para funcionar).
  • La clase puede acceder potencialmente a todos los servicios.

solución 2

Cree una clase que agrupe todos los servicios y páselos 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 independiente de IoC Container
  • La clase es descriptiva (en el aspecto de sus dependencias). Por convención, si quieres saber qué clase A Depende de que la información se acumule en A.Dependency
  • La firma del constructor se vuelve simple

Por qué no

  • necesidad de crear una clase adicional
  • el registro del servicio se vuelve complejo (Necesita registrarse cada X.Dependency por separado)
  • Conceptualmente igual que pasar IoC Container
  • ..

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

Respondido el 09 de junio de 17 a las 07:06

El solucionador inyectará dependencias el contexto de datos y las interfaces en el constructor. - León de Oro

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 enviando un resolutor y luego un montón de clases, pero asumimos que esas clases / objetos son en sí mismos servicios, listos para ser inyectados. ¿Y si no lo son?

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

Container.GetSevice<MyClass>(someObject1, someObject2)

... este formato rara vez es compatible. Creo que la dificultad de programar dicho soporte, sumado al miserable desempeño que estaría asociado 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 conviertan en un "servicio" solo por pasar datos. Si "anti-patrón" se trata de 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 marcos que pueden 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 es tan limpio como debería ser, pero estoy seguro de que hay algo en cualquier idioma que elija emplear.

Respondido el 12 de Septiembre de 18 a las 21:09

Inyectar el recipiente es un atajo del que finalmente te arrepentirás.

La sobreinyección no es el problema, suele ser un síntoma de otras fallas estructurales, sobre todo la separación de preocupaciones. Este no es un problema, pero puede tener muchas fuentes y lo que hace que esto sea tan difícil de solucionar es que tendrás que lidiar con todos ellos, a veces al mismo tiempo (piensa en desenredar los espaguetis).

Aquí hay una lista incompleta de las cosas a tener en cuenta

Diseño de dominio deficiente (raíz agregada ... etc.)

Mala separación de preocupaciones (composición del servicio, comandos, consultas). Consulte CQRS y abastecimiento de eventos.

O Mapeadores (tenga cuidado, estas cosas pueden causarle problemas)

Ver modelos y otros DTO (¡¡¡Nunca reutilices uno y trata de mantenerlos al mínimo !!)

Respondido 22 Oct 18, 06:10

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 crudo sobre cómo realizar las inyecciones y ejecutar el constructor después de inyectar valores. Este es un programa completamente 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

Respondido 20 ago 17, 13:08

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

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

Con Spring, podría pasar los valores requeridos con establecedores en su lugar y podría usar anotaciones @required para hacer cumplir que se inyectan. La desventaja es que debe mover el código de inicialización del constructor a otro método y hacer que Spring llame que después de que todas las dependencias se inyecten marcándolo con @PostConstruct. No estoy seguro de otros marcos, pero supongo que hacen algo similar.

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

respondido 11 mar '10, 05:03

La razón de la inyección del constructor es hacer que las dependencias sean obvias, no porque parezca más natural para los desarrolladores de Java. - L-Cuatro

Comentario tardío, pero esta respuesta me hizo reír :) - Federico Prijck

+1 para inyecciones basadas en incubadoras. Si tengo servicios y repositorios definidos en mi clase, es bastante obvio que son dependencias ... No necesito escribir constructores masivos con apariencia de VB6, y hacer una asignación estúpida de código en el constructor. Es bastante obvio cuáles son las dependencias en los campos requeridos. - Piotr Kula

Según 2018, Spring recomienda oficialmente no usar la inyección de setter, excepto para las dependencias que tienen un valor predeterminado razonable. Como en, si la dependencia es obligatoria para la clase, se recomienda la inyección del constructor. Ver discusión sobre setter vs ctor DI - Fulano de Tal

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.