¿Cómo evitar la locura del constructor de Dependency Injection?

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

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

con una lista de parámetros cada vez mayor. Como “Container” es mi contenedor de dependency injection, ¿por qué no puedo hacer esto simplemente?

 public MyClass(Container con) 

para cada clase? ¿Cuáles son los inconvenientes? Si hago esto, parece que estoy usando una estática glorificada. Por favor, comparta sus ideas sobre IoC y locura de Inyección de Dependencia.

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, lo considero un antipatrón .

Uno de los maravillosos beneficios de Constructor Injection es que hace que las violaciones del Principio de Responsabilidad Individual sean absolutamente obvias.

Cuando eso sucede, es hora de refactorizarse a Facade Services . En resumen, cree una nueva interfaz más gruesa que oculte la interacción entre algunas o todas las dependencias de grano fino que actualmente necesita.

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

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

La Inyección de Dependencia puede actuar como una advertencia temprana para que las clases sean demasiado grandes, específicamente debido al dolor creciente de pasar en todas las dependencias.

Me encontré con una pregunta similar sobre la dependency injection basada en el constructor y cuán compleja era llegar a 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 repositorys, usaría una inyección de setter de las propiedades privadas. Esto requiere crear una fábrica abstracta y mover la lógica de crear los repositorys en una fábrica.

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

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

Problema:

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

2) Si la clase es heredada (Ej: RepositoryBase ), entonces el cambio de la firma del constructor causa cambios en las clases derivadas.

Solución 1

Pase 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 su clase se acople estrechamente 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 se puede mirar al constructor de la clase y decir lo que necesita para funcionar).
  • La clase puede acceder potencialmente a todo el servicio.

Solución 2

Crea una clase que agrupe todo el servicio y pásalo 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 las clases derivadas
  • La clase es agnóstica de IoC Container
  • La clase es descriptiva (en el aspecto de sus dependencias). Por convención, si desea saber de qué clase A Depende, esa información se acumula 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 (debe registrar cada X.Dependency separado)
  • Conceptualmente lo mismo que pasar IoC Container
  • ..

La solución 2 es simplemente cruda, si hay un argumento sólido en contra de ella, entonces el comentario descriptivo sería apreciado

Este es el enfoque que uso

 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 de cómo realizar las inyecciones y ejecutar constructor después de inyectar valores. Este es un progtwig 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 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 hobby que funciona así https://github.com/Jokine/ToolProject/tree/Core

¿Qué marco de dependency injection estás usando? ¿Has probado usar la inyección basada en setter en su lugar?

El beneficio para la inyección basada en el constructor es que parece natural para los progtwigdores de Java que no usan marcos DI. Necesitas 5 cosas para inicializar una clase y luego tienes 5 argumentos para tu constructor. La desventaja es lo que has notado, se vuelve difícil cuando tienes muchas dependencias.

Con Spring, en su lugar, podría pasar los valores requeridos con setters y podría usar las anotaciones requeridas para exigir que sean inyectados. El inconveniente es que debe mover el código de inicialización del constructor a otro método y hacer que Spring invoque después de que todas las dependencias se hayan inyectado marcándolo con @PostConstruct. No estoy seguro de otros marcos, pero supongo que hacen algo similar.

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