L'injection de dépendances doit-elle être effectuée dans le ctor ou par méthode?


16

Considérer:

public class CtorInjectionExample
{
    public CtorInjectionExample(ISomeRepository SomeRepositoryIn, IOtherRepository OtherRepositoryIn)
    {
        this._someRepository = SomeRepositoryIn;
        this._otherRepository = OtherRepositoryIn;
    }

    public void SomeMethod()
    {
        //use this._someRepository
    }

    public void OtherMethod()
    {
        //use this._otherRepository
    }
}

contre:

public class MethodInjectionExample
{
    public MethodInjectionExample()
    {
    }

    public void SomeMethod(ISomeRepository SomeRepositoryIn)
    {
        //use SomeRepositoryIn
    }

    public void OtherMethod(IOtherRepository OtherRepositoryIn)
    {
        //use OtherRepositoryIn
    }
}

Bien que l'injection de Ctor rend l'extension difficile (tout code appelant le ctor devra être mis à jour lorsque de nouvelles dépendances sont ajoutées), et l'injection au niveau de la méthode semble rester plus encapsulée à partir de la dépendance au niveau de la classe et je ne trouve aucun autre argument pour / contre ces approches .

Existe-t-il une approche définitive à adopter pour l'injection?

(NB: j'ai cherché des informations à ce sujet et j'ai essayé de rendre cette question objective.)


4
Si vos classes sont cohérentes, de nombreuses méthodes différentes utiliseraient les mêmes champs. Cela devrait vous donner la réponse que vous cherchez ...
Oded

@Oded Bon point, la cohésion influencerait fortement la décision. Si une classe est, disons, des collections de méthodes auxiliaires qui ont chacune des dépendances différentes (apparemment pas cohésives, mais logiquement similaires) et une autre est très cohésive, elles devraient chacune utiliser l'approche la plus avantageuse. Cependant, cela entraînerait une incohérence dans une solution.
StuperUser

Pertinent pour les exemples que j'ai donnés: en.wikipedia.org/wiki/False_dilemma
StuperUser

Réponses:


3

Cela dépend, si l'objet injecté est utilisé par plus d'une méthode dans la classe et que l'injection dans chaque méthode n'a pas de sens, vous devez résoudre les dépendances pour chaque appel de méthode, mais si la dépendance n'est utilisée que par une méthode qui y est injectée sur le constructeur n'est pas bonne, mais puisque vous allouez des ressources qui ne pourront jamais être utilisées.


15

Bon, tout d'abord, parlons de "Tout code appelant le ctor devra être mis à jour lorsque de nouvelles dépendances seront ajoutées"; Pour être clair, si vous faites une injection de dépendance et que vous avez du code appelant new () sur un objet avec des dépendances, vous le faites mal .

Votre conteneur DI doit pouvoir injecter toutes les dépendances pertinentes, vous n'avez donc pas à vous soucier de changer la signature du constructeur, afin que cet argument ne tienne pas vraiment.

Quant à l'idée d'injection par méthode vs par classe, il y a deux problèmes majeurs avec l'injection par méthode.

Un problème est que les méthodes de votre classe doivent partager les dépendances, c'est une façon de vous assurer que vos classes sont séparées efficacement, si vous voyez une classe avec un grand nombre de dépendances (probablement plus de 4-5), alors cette classe est un candidat de choix pour refactoring en deux classes.

Le problème suivant est que pour «injecter» les dépendances, par méthode, vous devez les transmettre à l'appel de méthode. Cela signifie que vous devrez résoudre les dépendances avant l'appel de méthode, vous vous retrouverez donc probablement avec un tas de code comme celui-ci:

var someDependency = ServiceLocator.Resolve<ISomeDependency>();
var something = classBeingInjected.DoStuff(someDependency);

Maintenant, disons que vous allez appeler cette méthode à 10 endroits autour de votre application: vous aurez 10 de ces extraits. Ensuite, disons que vous devez ajouter une autre dépendance à DoStuff (): vous devrez modifier cet extrait 10 fois (ou l'encapsuler dans une méthode, auquel cas vous ne faites que répliquer le comportement DI manuellement, ce qui est un gaspillage de temps).

Donc, ce que vous avez fait là-bas, c'est que vos classes utilisant DI sont conscientes de leur propre conteneur DI, ce qui est fondamentalement une mauvaise idée , car cela conduit très rapidement à une conception maladroite difficile à maintenir.

Comparez cela à l'injection de constructeur; Dans l'injection de constructeur, vous n'êtes pas lié à un conteneur DI particulier, et vous n'êtes jamais directement responsable de remplir les dépendances de vos classes, donc la maintenance est assez sans maux de tête.

Il me semble que vous essayez d'appliquer IoC à une classe contenant un tas de méthodes d'assistance non liées, alors que vous feriez mieux de diviser la classe d'assistance en un certain nombre de classes de service en fonction de l'utilisation, puis d'utiliser l'aide pour déléguer la appels. Ce n'est toujours pas une excellente approche (une aide classée avec des méthodes qui font quelque chose de plus complexe que de simplement traiter les arguments qui leur sont transmis ne sont généralement que des classes de service mal écrites), mais cela gardera au moins votre conception un peu plus propre .

(NB j'ai fait l'approche que vous proposez avant, et c'était une très mauvaise idée que je n'ai pas répétée depuis. Il s'est avéré que j'essayais de séparer les classes qui n'avaient vraiment pas besoin d'être séparées, et j'ai fini par avec un ensemble d'interfaces où chaque appel de méthode nécessitait une sélection presque fixe d'autres interfaces. C'était un cauchemar à entretenir.)


1
"if you're doing dependency injection and you have any code calling new() on an object with dependencies, you're doing it wrong."Si vous testez une méthode sur une classe, vous devez l'instancier ou utilisez-vous DI pour instancier votre code sous test?
StuperUser

Le test @StuperUser Unit est un scénario légèrement différent, mon commentaire ne s'applique vraiment qu'au code de production. Comme vous allez créer des dépendances fictives (ou stub) pour vos tests via un framework de simulation, chacun avec un comportement préconfiguré et un ensemble d'hypothèses et d'assertions, vous devrez les injecter à la main.
Ed James

1
C'est le code dont je parle quand je dis "any code calling the ctor will need updating", mon code de production n'appelle pas les ctors. Pour ces raisons.
StuperUser

2
@StuperUser Assez bien, mais vous allez toujours appeler les méthodes avec les dépendances requises, ce qui signifie que le reste de ma réponse est toujours valable! Pour être honnête, je suis d'accord sur le problème de l'injection de constructeur et du forçage de toutes les dépendances, du point de vue des tests unitaires. J'ai tendance à utiliser l'injection de propriétés, car cela vous permet d'injecter uniquement les dépendances dont vous vous attendez pour un test, ce que je trouve fondamentalement un autre ensemble d'appels implicites Assert.WasNotCalled sans avoir à écrire de code! : D
Ed James

3

Il existe différents types d' inversion de contrôle , et votre question n'en propose que deux (vous pouvez également utiliser l'usine pour injecter la dépendance).

La réponse est: cela dépend du besoin. Parfois, il vaut mieux utiliser un type et un autre type différent.

Personnellement, j'aime les injecteurs constructeurs, car le constructeur est là pour initialiser complètement l'objet. Devoir appeler un setter plus tard rend l'objet pas entièrement construit.


3

Vous avez manqué celui qui, au moins pour moi, est l'injection de propriété la plus flexible. J'utilise Castle / Windsor et tout ce que je dois faire est d'ajouter une nouvelle propriété auto à ma classe et j'obtiens l'objet injecté sans aucun problème et sans casser aucune interface.


Je suis habitué aux applications Web et si ces classes sont dans la couche Domaine, appelée par l'interface utilisateur, dont les dépendances sont déjà résolues de la même manière que l'injection de propriété. Je me demande comment gérer les classes auxquelles je peux transmettre les objets injectés.
StuperUser

J'aime aussi l'injection de propriétés, simplement pour que vous puissiez toujours utiliser les paramètres de constructeur normaux avec un conteneur DI, en faisant automatiquement la différence entre les dépendances résolues et non résolues. Cependant, comme l'injection de constructeur et de propriété est fonctionnellement identique pour ce scénario, j'ai choisi de ne pas le mentionner;)
Ed James

L'injection de propriété force chaque objet à être mutable et crée des instances dans un état non valide. Pourquoi serait-ce préférable?
Chris Pitman

2
@Chris En fait, dans des conditions normales, le constructeur et l'injection de propriété agissent à peu près de la même manière, l'objet étant complètement injecté pendant la résolution. Le seul moment où il peut être considéré comme "non valide" est pendant les tests unitaires, lorsque vous n'allez pas utiliser le conteneur DI pour instancier l'implémentation. Voir mon commentaire sur ma réponse pour savoir pourquoi c'est réellement bénéfique. J'apprécie que la mutabilité pourrait être un problème, mais en réalité, vous ne référencerez les classes résolues que via leurs interfaces, ce qui n'exposera pas les dépendances à modifier.
Ed James
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.