web-dev-qa-db-fra.com

Comment éviter la folie du constructeur Injection de dépendance?

Je trouve que mes constructeurs commencent à ressembler à ceci:

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

avec liste de paramètres toujours croissante. Puisque "Container" est mon conteneur d'injection de dépendance, 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 une image statique glorifiée. S'il vous plaît partagez vos pensées sur IoC et la folie d'injection de dépendance.

265
JP Richardson

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

L'un des avantages remarquables de Constructor Injection est qu'il rend les violations du principe de responsabilité unique évidentes.

Lorsque cela se produit, il est temps de passer au refactor to Facade Services . En bref, créez une nouvelle interface, plus coarse-grained, qui masque l’interaction entre tout ou partie des dépendances à grain fin actuellement requises.

363
Mark Seemann

Je ne pense pas que les constructeurs de classe 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 IOC tente d'éviter!). 

57
derivation

La difficulté de passer dans les paramètres n'est pas le problème. Le problème est que votre classe en fait trop et devrait être décomposée davantage.

L'injection de dépendance peut constituer un avertissement précoce pour les classes devenant trop grandes, notamment en raison de la douleur croissante liée au dépassement de toutes les dépendances.

24
kyoryu

Je suis tombé sur une question similaire à propos de l'injection de dépendance basée sur le constructeur et de la complexité de son intégration dans toutes les dépendances.

L’une des approches que j’ai utilisée par le passé consiste à 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 utilisera une injection de sélecteur des propriétés privées. Cela nécessite la création d'une fabrique abstraite et le déplacement de la logique de création des référentiels dans une fabrique.

Le code détaillé avec l'explication peut être trouvé ici

Meilleures pratiques pour IoC dans la couche de services complexes

3
samsur

Problème:  

1) Constructeur avec une liste de paramètres toujours croissante.

2) Si la classe est héritée (Ex: RepositoryBase), le changement de constructeur Signature entraîne une modification des classes dérivées.

Solution 1

Passer IoC Container au constructeur 

Pourquoi

  • Pas plus liste de paramètres toujours croissante
  • La signature du constructeur devient simple

Pourquoi pas

  • Vous permet de classe étroitement couplé à 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éez une classe qui regroupe tous les services et transmettez-la 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 de IoC Container
  • La classe est descriptive (en ce qui concerne ses dépendances). Par convention, si vous voulez savoir de quelle classe A dépend, ces informations sont accumulées 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 au passage de IoC Container
  • .. 

La solution 2 n’est cependant qu’une idée brute, mais si un argument solide s’y oppose, un commentaire descriptif serait apprécié

1
tchelidze

L'injection du conteneur est un raccourci que vous regretterez finalement.

La surinjection n’est pas le problème, elle est généralement le symptôme d’autres défauts structurels, notamment la séparation des préoccupations. Ce n’est pas un problème mais peut avoir plusieurs sources et ce qui rend la tâche si difficile à résoudre, c’est que vous allez devoir toutes les traiter, parfois en même temps (pensez à démêler des spaghettis). 

Voici une liste incomplète des choses à surveiller

Conception de domaine médiocre (racine agrégée, etc.)

Mauvaise séparation des préoccupations (composition du service, commandes, requêtes) Voir CQRS et Event Sourcing.

OU Mappeurs (soyez prudent, ces choses peuvent vous causer des ennuis)

Voir les modèles et les autres DTO (ne jamais en réutiliser et essayer de les garder au minimum !!!!)

0
Marius

J'ai lu tout ce fil, deux fois, et je pense que les gens répondent par ce qu'ils savent, pas par ce qu'on leur demande.

La question initiale de JP donne l'impression qu'il construit des objets en envoyant un résolveur, puis un groupe de classes, mais nous supposons que ces classes/objets sont eux-mêmes des services, prêts à être injectés. Et s'ils ne le sont pas?

JP, si vous cherchez à tirer parti de DI et si vous souhaitez mélanger l'injection avec des données contextuelles, aucun de ces schémas (ou supposés "anti-schémas") ne répond spécifiquement à cela. Cela revient en fait à utiliser un package qui vous aidera dans cette tâche.

Container.GetSevice<MyClass>(someObject1, someObject2)

... ce format est rarement pris en charge. Je crois que la difficulté de programmer un tel support, ajouté aux performances misérables qui seraient associées à la mise en œuvre, le rend moins attrayant pour les développeurs open source.

Mais cela devrait être fait, car je devrais être capable de créer et d’enregistrer une fabrique pour MyClass, et cette fabrique devrait pouvoir recevoir des données/entrées qui ne sont pas poussées à être un "service" juste pour le plaisir de passer. Les données. Si "anti-pattern" concerne des conséquences négatives, alors forcer l'existence de types de services artificiels pour transmettre des données/modèles est certainement négatif (à égalité avec votre sentiment d'enrober vos classes dans un conteneur. Le même instinct s'applique).

Il existe un cadre qui peut aider, même s’ils ont l’air un peu moche. Par exemple, Ninject:

Création d'une instance à l'aide de Ninject avec des paramètres supplémentaires dans le constructeur

C’est pour .NET, il est populaire et n’est toujours pas aussi propre qu’il devrait être, mais je suis sûr qu’il ya quelque chose dans la langue que vous choisissez d’employer.

0
Craig Brunetti

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 le constructeur après l’injection de valeurs. Ceci est un programme entièrement fonctionnel.

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

    }
}

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

0
Ronijo