Comment éviter la folie des constructeurs Dependency Injection?

Je trouve que mes constructeurs commencent à ressembler à ceci:

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

avec une liste de parameters toujours croissante. Puisque “Container” est mon conteneur d’dependency injections, pourquoi ne puis-je pas simplement faire ceci:

 public MyClass(Container con) 

pour chaque classe? Quels sont les inconvénients? Si je fais cela, j’ai l’impression d’utiliser un statique glorifié. S’il vous plaît partager vos reflections sur la folie IoC et l’dependency injection.

Vous avez raison de dire que si vous utilisez le conteneur en tant que localisateur de service, il s’agit plus ou moins d’une usine statique glorifiée. Pour beaucoup de raisons, je considère cela comme un anti-modèle .

L’un des grands avantages de l’injection par constructeur est qu’elle rend les violations du principe de la responsabilité unique manifestement évidentes.

Lorsque cela se produit, il est temps de réorganiser les services de façade . En résumé, créez une nouvelle interface plus grossière qui masque l’interaction entre certaines ou toutes les dépendances fines dont vous avez actuellement besoin.

Je ne pense pas que vos constructeurs de classes devraient avoir une référence à votre période de conteneur IOC. Cela représente une dépendance inutile entre votre classe et le conteneur (le type de dépendance que IOC tente d’éviter!).

La difficulté de transmettre les parameters n’est pas le problème. Le problème est que votre classe en fait trop et devrait être décomposée davantage.

L’dependency injection peut agir comme un avertissement précoce pour les classes devenant trop grandes, en particulier à cause de la douleur croissante liée à la transmission de toutes les dépendances.

Je suis tombé sur une question similaire à propos de l’dependency injection basée sur le constructeur et sur la complexité de la transmission dans toutes les dépendances.

L’une des approches que j’ai utilisées dans le passé est d’utiliser le motif de façade de l’application en utilisant une couche de service. Cela aurait une API grossière. Si ce service dépend de référentiels, il utiliserait une injection de setter des propriétés privées. Cela nécessite de créer une fabrique abstraite et de déplacer la logique de création des référentiels dans une usine.

Code détaillé avec explication peut être trouvé ici

Meilleures pratiques pour l’IoC dans la couche de service complexe

Problème:

1) Constructeur avec liste de parameters toujours croissante.

2) Si class est hérité (Ex: RepositoryBase ), la modification de la signature du constructeur entraîne un changement dans les classes dérivées.

Solution 1

Passer le IoC Container au constructeur

Pourquoi

  • Plus de liste de parameters sans cesse croissante
  • La signature du constructeur devient simple

Pourquoi pas

  • Cela vous rend étroitement couplé au conteneur IoC. (Cela pose des problèmes lorsque 1. vous souhaitez utiliser cette classe dans d’autres projets où vous utilisez un conteneur IoC différent. 2. vous décidez de modifier le conteneur IoC)
  • Vous rend la classe moins descriptive. (Vous ne pouvez pas vraiment regarder le constructeur de classe et dire ce dont il a besoin pour fonctionner.)
  • La classe peut accéder potentiellement à tous les services.

Solution 2

Créer une classe qui regroupe tous les services et les transmettre au constructeur

  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; } } 

Classe dérivée

  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; } } 

Pourquoi

  • L’ajout d’une nouvelle dépendance à la classe n’affecte pas les classes dérivées
  • La classe est agnostique du conteneur IoC
  • La classe est descriptive (dans l’aspect de ses dépendances). Par convention, si vous voulez savoir de quelle classe dépend la A.Dependency , cette information est accumulée dans A.Dependency
  • La signature du constructeur devient simple

Pourquoi pas

  • besoin de créer une classe supplémentaire
  • l’enregistrement du service devient complexe (vous devez enregistrer chaque X.Dependency séparément)
  • Conceptuellement identique à celle du IoC Container
  • ..

La solution 2 n’est qu’un brut, si des arguments solides s’y opposent, alors un commentaire descriptif serait apprécié

C’est l’approche que j’utilise

 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; } } 

Voici une approche grossière pour effectuer les injections et exécuter un constructeur après avoir injecté des valeurs. C’est un programme entièrement fonctionnel.

 public class InjectAtsortingbute : Atsortingbute { } 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(ssortingng[] 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.GetCustomAtsortingbutes(typeof(InjectAtsortingbute), 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); } } 

Je travaille actuellement sur un projet de loisir qui fonctionne comme ceci https://github.com/Jokine/ToolProject/tree/Core

Quel cadre d’dependency injection utilisez-vous? Avez-vous essayé d’utiliser l’injection basée sur setter à la place?

L’avantage de l’injection basée sur un constructeur est qu’il semble naturel pour les programmeurs Java qui n’utilisent pas les frameworks DI. Vous avez besoin de 5 choses pour initialiser une classe, alors vous avez 5 arguments pour votre constructeur. L’inconvénient est ce que vous avez remarqué, il devient difficile lorsque vous avez beaucoup de dépendances.

Avec Spring, vous pouvez passer les valeurs requirejses avec setters et vous pouvez utiliser les annotations @required pour imposer qu’elles soient injectées. L’inconvénient est que vous devez déplacer le code d’initialisation du constructeur vers une autre méthode et appeler Spring après que toutes les dépendances aient été injectées en le marquant avec @PostConstruct. Je ne suis pas sûr des autres frameworks mais je suppose qu’ils font quelque chose de similaire.

Les deux manières fonctionnent, c’est une question de préférence.