web-dev-qa-db-fra.com

Injection de dépendance par le biais de constructeurs ou de promoteurs immobiliers?

Je refactorise une classe et y ajoute une nouvelle dépendance. La classe prend actuellement ses dépendances existantes dans le constructeur. Donc, pour plus de cohérence, j'ajoute le paramètre au constructeur.
Bien sûr, il y a quelques sous-classes et encore plus pour les tests unitaires, alors maintenant je joue le jeu de modifier tous les constructeurs pour correspondre, et cela prend des âges.
Cela me fait penser que l'utilisation de propriétés avec des setters est un meilleur moyen d'obtenir des dépendances. Je ne pense pas que les dépendances injectées devraient faire partie de l'interface pour construire une instance d'une classe. Vous ajoutez une dépendance et maintenant tous vos utilisateurs (sous-classes et toute personne qui vous instancie directement) le savent soudainement. Cela ressemble à une pause d'encapsulation.

Cela ne semble pas être le modèle avec le code existant ici, donc je cherche à savoir quel est le consensus général, les avantages et les inconvénients des constructeurs par rapport aux propriétés. Est-il préférable d'avoir recours à des évaluateurs immobiliers?

144

En fait ça dépend :-).

Si la classe ne peut pas faire son travail sans la dépendance, ajoutez-la au constructeur. La classe a besoin la nouvelle dépendance, donc vous voulez que votre changement casse les choses. En outre, la création d'une classe qui n'est pas entièrement initialisée ("construction en deux étapes") est un anti-modèle (IMHO).

Si la classe peut fonctionner sans la dépendance, un setter est très bien.

124
sleske

Les utilisateurs d'une classe sont supposés pour connaître les dépendances d'une classe donnée. Si j'avais une classe qui, par exemple, se connectait à une base de données et ne fournissait pas de moyen pour injecter une dépendance de couche de persistance, un utilisateur ne saurait jamais qu'une connexion à la base de données devrait être disponible. Cependant, si je modifie le constructeur, je fais savoir aux utilisateurs qu'il existe une dépendance à la couche de persistance.

De plus, pour vous éviter d'avoir à modifier chaque utilisation de l'ancien constructeur, appliquez simplement le chaînage du constructeur comme pont temporaire entre l'ancien et le nouveau constructeur.

public class ClassExample
{
    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo)
        : this (dependnecyOne, dependencyTwo, new DependnecyThreeConcreteImpl())
    { }

    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo, IDependencyThree dependencyThree)
    {
        // Set the properties here.
    }
}

L'un des points de l'injection de dépendances est de révéler les dépendances de la classe. Si la classe a trop de dépendances, il peut être temps de refactoriser: Est-ce que chaque méthode de la classe utilise toutes les dépendances? Sinon, c'est un bon point de départ pour voir où la classe pourrait être divisée.

20
Scott

Bien sûr, mettre le constructeur signifie que vous pouvez tout valider en même temps. Si vous affectez des éléments dans des champs en lecture seule, vous avez des garanties sur les dépendances de votre objet dès la construction.

C'est vraiment difficile d'ajouter de nouvelles dépendances, mais au moins de cette façon, le compilateur continue de se plaindre jusqu'à ce qu'il soit correct. Ce qui est une bonne chose, je pense.

17
Joe

Si vous avez un grand nombre de dépendances optionnelles (ce qui est déjà une odeur), l'injection de setter est probablement la voie à suivre. L'injection de constructeur révèle cependant mieux vos dépendances.

11
epitka

L'approche généralement préférée consiste à utiliser autant que possible l'injection de constructeur.

L'injection de constructeur indique exactement quelles sont les dépendances requises pour que l'objet fonctionne correctement - rien n'est plus ennuyeux que de renouveler un objet et de le faire planter lors de l'appel d'une méthode car une dépendance n'est pas définie. L'objet renvoyé par un constructeur doit être dans un état de fonctionnement.

Essayez d'avoir un seul constructeur, il garde la conception simple et évite toute ambiguïté (sinon pour les humains, pour le conteneur DI).

Vous pouvez utiliser l'injection de propriété lorsque vous avez ce que Mark Seemann appelle un défaut local dans son livre "Dependency Injection in .NET": la dépendance est facultative car vous pouvez fournir une implémentation de travail fine mais vous voulez autoriser le appelant pour spécifier un autre si nécessaire.

(Ancienne réponse ci-dessous)


Je pense que l'injection constructeur est meilleure si l'injection est obligatoire. Si cela ajoute trop de constructeurs, envisagez d'utiliser des usines au lieu de constructeurs.

L'injection de setter est Nice si l'injection est facultative, ou si vous voulez la changer à mi-chemin. Je n'aime généralement pas les setters, mais c'est une question de goût.

11
Philippe

C'est en grande partie une question de goût personnel. Personnellement, j'ai tendance à préférer l'injection de setter, car je pense que cela vous donne plus de flexibilité dans la façon dont vous pouvez remplacer les implémentations au moment de l'exécution. De plus, les constructeurs avec beaucoup d'arguments ne sont pas propres à mon avis, et les arguments fournis dans un constructeur devraient être limités à des arguments non optionnels.

Tant que l'interface de classes (API) est claire dans ce dont elle a besoin pour effectuer sa tâche, vous êtes bon.

7
nkr1pt

Personnellement, je préfère le Extract and Override "pattern" à l'injection de dépendances dans le constructeur, en grande partie pour la raison décrite dans votre question. Vous pouvez définir les propriétés comme virtual, puis remplacer l'implémentation dans une classe testable dérivée.

7
Russ Cam

Je préfère l'injection de constructeur car elle aide à "faire respecter" les exigences de dépendance d'une classe. Si c'est dans le c'tor, un consommateur has pour définir les objets pour obtenir l'application à compiler. Si vous utilisez l'injection de setter, il se peut qu'ils ne sachent pas qu'ils ont un problème jusqu'au moment de l'exécution - et en fonction de l'objet, il peut être tardif lors de l'exécution.

J'utilise toujours l'injection de setter de temps en temps lorsque l'objet injecté a peut-être besoin d'un tas de travail lui-même, comme l'initialisation.

6
ctacke

Je fais une injection de constructeur, car cela semble plus logique. C'est comme dire ma classe nécessite ces dépendances pour faire son travail. S'il s'agit d'une dépendance facultative, les propriétés semblent raisonnables.

J'utilise également l'injection de propriétés pour définir des éléments auxquels le conteneur n'a pas de référence, comme une vue ASP.NET sur un présentateur créé à l'aide du conteneur.

Je ne pense pas que cela brise l'encapsulation. Le fonctionnement interne doit rester interne et les dépendances doivent répondre à une préoccupation différente.

6
David Kiff

J'ai récemment rencontré une situation où j'avais plusieurs dépendances dans une classe, mais une seule des dépendances allait nécessairement changer dans chaque implémentation. Étant donné que les dépendances d'accès aux données et de journalisation des erreurs ne seraient probablement modifiées qu'à des fins de test, j'ai ajouté des paramètres facultatifs pour ces dépendances et fourni des implémentations par défaut de ces dépendances dans mon code constructeur. De cette façon, la classe conserve son comportement par défaut à moins d'être remplacée par le consommateur de la classe.

L'utilisation de paramètres facultatifs ne peut être effectuée que dans des cadres qui les prennent en charge, tels que .NET 4 (pour C # et VB.NET, bien que VB.NET les ait toujours eu). Bien sûr, vous pouvez accomplir des fonctionnalités similaires en utilisant simplement une propriété qui peut être réaffectée par le consommateur de votre classe, mais vous n'obtenez pas l'avantage de l'immuabilité en ayant un objet d'interface privé affecté à un paramètre du constructeur.

Tout cela étant dit, si vous introduisez une nouvelle dépendance qui doit être fournie par chaque consommateur, vous devrez refactoriser votre constructeur et tout le code que les consommateurs de votre classe. Mes suggestions ci-dessus ne s'appliquent vraiment que si vous avez le luxe de pouvoir fournir une implémentation par défaut pour tout votre code actuel, mais offrent toujours la possibilité de remplacer l'implémentation par défaut si nécessaire.

3
Ben McCormack

Une option qui pourrait être intéressante est de composer des dépendances multiples complexes à partir de simples dépendances simples. Autrement dit, définissez des classes supplémentaires pour les dépendances composées. Cela rend les choses un peu plus faciles à injecter par constructeur WRT - moins de paramètres par appel - tout en conservant la chose must-supply-all-dependencies-to-instancier.

Bien sûr, cela a plus de sens s'il y a une sorte de regroupement logique des dépendances, donc le composé est plus qu'un agrégat arbitraire, et cela a plus de sens s'il y a plusieurs dépendants pour une seule dépendance composée - mais le bloc de paramètres "pattern" a existe depuis longtemps, et la plupart de ceux que j'ai vus ont été assez arbitraires.

Personnellement, cependant, je suis plus fan de l'utilisation de méthodes/propriété-setters pour spécifier les dépendances, les options, etc. Les noms d'appels aident à décrire ce qui se passe. C'est une bonne idée de fournir un exemple d'extraits de ceci, cependant, et de vous assurer que la classe dépendante effectue suffisamment de vérifications d'erreurs. Vous souhaiterez peut-être utiliser un modèle à états finis pour la configuration.

3
Steve314

Il s'agit d'un ancien article, mais s'il est nécessaire à l'avenir, il peut être utile:

https://github.com/omegamit6zeichen/prinject

J'ai eu une idée similaire et j'ai trouvé ce cadre. Il est probablement loin d'être achevé, mais il s'agit d'une idée d'un cadre centré sur l'injection de propriété

1
Markus

L'injection de constructeur révèle explicitement les dépendances, ce qui rend le code plus lisible et moins sujet aux erreurs d'exécution non gérées si les arguments sont vérifiés dans le constructeur, mais cela revient vraiment à une opinion personnelle, et plus vous utilisez DI, plus vous ont tendance à osciller d'avant en arrière d'une manière ou d'une autre selon le projet. Personnellement, j'ai des problèmes avec les odeurs de code comme les constructeurs avec une longue liste d'arguments, et je pense que le consommateur d'un objet devrait connaître les dépendances afin d'utiliser l'objet de toute façon, donc cela justifie l'utilisation de l'injection de propriété. Je n'aime pas la nature implicite de l'injection de propriété, mais je la trouve plus élégante, ce qui donne un code plus propre. Mais d'un autre côté, l'injection de constructeur offre un degré d'encapsulation plus élevé, et d'après mon expérience, j'essaie d'éviter les constructeurs par défaut, car ils peuvent avoir un effet néfaste sur l'intégrité des données encapsulées si l'on ne fait pas attention.

Choisissez judicieusement l'injection par constructeur ou par bien en fonction de votre scénario spécifique. Et ne pensez pas que vous devez utiliser DI simplement parce que cela semble nécessaire et cela évitera les mauvaises odeurs de conception et de code. Parfois, cela ne vaut pas la peine d'utiliser un modèle si l'effort et la complexité l'emportent sur l'avantage. Rester simple.

0
David Spenard

Cela dépend de la façon dont vous souhaitez l'implémenter. Je préfère l'injection de constructeur partout où je sens que les valeurs qui entrent dans l'implémentation ne changent pas souvent. Par exemple: si la stratégie compnay est associée au serveur Oracle, je configurerai mes valeurs de source de données pour un bean établissant des connexions via l'injection de constructeur. Sinon, si mon application est un produit et qu'elle peut se connecter à n'importe quelle base de données du client, j'implémenterais une telle configuration de base de données et une implémentation multi-marques via l'injection de setter. Je viens de prendre un exemple, mais il existe de meilleures façons de mettre en œuvre les scénarios que j'ai mentionnés ci-dessus.

0
ramarao Gangina