Injection de dépendances via les constructeurs ou les setters de propriété?

151

Je refactore une classe et y ajoute une nouvelle dépendance. La classe prend actuellement ses dépendances existantes dans le constructeur. Donc, par souci 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 au jeu de modifier tous les constructeurs pour qu'ils correspondent, et cela prend des années.
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 de construction d'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 rupture d'encapsulation.

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

Niall Connaughton
la source

Réponses:

126

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 de la nouvelle dépendance, vous voulez donc que votre changement brise 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 convient.

sleske
la source
11
Je pense que dans de nombreux cas, il est préférable d'utiliser le modèle Null Object et de s'en tenir à exiger les références sur le constructeur. Cela évite toutes les vérifications nulles et l'augmentation de la complexité cyclomatique.
Mark Lindell
3
@Mark: Bon point. Cependant, la question portait sur l'ajout d'une dépendance à une classe existante. Ensuite, garder un constructeur sans argument permet une compatibilité descendante.
sleske
Qu'en est-il lorsque la dépendance est nécessaire pour fonctionner, mais une injection par défaut de cette dépendance suffit généralement. Alors cette dépendance devrait-elle être «remplaçable» par une propriété ou une surcharge de constructeur?
Patrick Szalapski
@Patrick: Par "la classe ne peut pas faire son travail sans la dépendance", je voulais dire qu'il n'y a pas de valeur par défaut raisonnable (par exemple, la classe nécessite une connexion à la base de données). Dans votre situation, les deux fonctionneraient. J'opterais toujours pour l'approche constructeur, car elle réduit la complexité (et si par exemple le setter est appelé deux fois)?
sleske
1
Un bon article à ce sujet ici explorejava.com/different-types-of-bean-injection-in-spring
Eldhose Abraham
21

Les utilisateurs d'une classe sont censés connaître les dépendances d'une classe donnée. Si j'avais une classe qui, par exemple, était connectée à une base de données et ne fournissait pas un moyen d'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 éviter d'avoir à modifier chaque utilisation de l'ancien constructeur, appliquez simplement le chaînage du constructeur comme un 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, alors il est peut-être temps pour une refactorisation d'avoir lieu: 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.

Docteur Blue
la source
Bien sûr, le chaînage du constructeur ne fonctionne que s'il existe une valeur par défaut raisonnable pour le nouveau paramètre. Mais sinon, vous ne pouvez pas éviter de casser des choses de toute façon ...
sleske
Habituellement, vous utiliseriez ce que vous utilisiez dans la méthode avant l'injection de dépendances comme paramètre par défaut. Idéalement, cela ferait du nouvel ajout de constructeur une refactorisation propre, car le comportement de la classe ne changerait pas.
Doctor Blue
1
Je prends note de votre point sur les dépendances de gestion des ressources telles que les connexions à la base de données. Je pense que le problème dans mon cas est que la classe à laquelle j'ajoute une dépendance a plusieurs sous-classes. Dans un monde conteneur IOC où la propriété serait définie par le conteneur, l'utilisation du setter soulagerait au moins la pression sur la duplication d'interface constructeur entre toutes les sous-classes.
Niall Connaughton
17

Bien sûr, mettre le constructeur signifie que vous pouvez tout valider en même temps. Si vous attribuez 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 pénible d'ajouter de nouvelles dépendances, mais au moins de cette façon, le compilateur continue de se plaindre jusqu'à ce que ce soit correct. Ce qui est une bonne chose, je pense.

Joe
la source
Plus un pour ça. De plus, cela réduit considérablement le danger de dépendances circulaires ...
gilgamash
11

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

epitka
la source
11

L'approche générale 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 créer un nouvel 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 opérationnel.

Essayez de n'avoir qu'un seul constructeur, cela 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 une valeur par défaut locale dans son livre «Dependency Injection in .NET»: la dépendance est facultative car vous pouvez fournir une implémentation fonctionnelle mais souhaitez permettre à l'appelant d'en spécifier une différente 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 agréable si l'injection est facultative, ou si vous souhaitez la changer à mi-chemin. Je n'aime généralement pas les setters, mais c'est une question de goût.

Philippe
la source
Je dirais que changer généralement l'injection à mi-chemin est un mauvais style (parce que vous ajoutez un état caché à votre objet). Mais pas de règle sans exception bien sûr ...
sleske
Oui, c'est pourquoi j'ai dit que je n'aimais pas trop le setter ... J'aime l'approche du constructeur car elle ne peut pas être modifiée.
Philippe
"Si cela ajoute trop de constructeurs, envisagez d'utiliser des usines au lieu de constructeurs." En gros, vous reportez toutes les exceptions d'exécution et vous pourriez même avoir des problèmes et vous retrouver coincé avec une implémentation de localisateur de services.
MeTitus
@Marco c'est ma réponse précédente et vous avez raison. S'il y a beaucoup de constructeurs, je dirais que la classe fait trop de choses :-) Ou considérons une fabrique abstraite.
Philippe le
7

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 substituer 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 des classes (API) est claire dans ce dont elle a besoin pour effectuer sa tâche, vous êtes bon.

nkr1pt
la source
veuillez préciser pourquoi vous votez contre?
nkr1pt
3
Oui, les constructeurs avec beaucoup d'arguments sont mauvais. C'est pourquoi vous refactorisez les classes avec beaucoup de paramètres de constructeur :-).
sleske
10
@ nkr1pt: La plupart des gens (moi y compris) conviennent que l'injection de setter est mauvaise si elle vous permet de créer une classe qui échoue à l'exécution si l'injection n'est pas faite. Je crois que quelqu'un s'est donc opposé à ce que vous disiez qu'il s'agissait d'un goût personnel.
sleske
7

Personnellement, je préfère le "modèle" d' extraction et de remplacement à l'injection de dépendances dans le constructeur, en grande partie pour la raison exposée dans votre question. Vous pouvez définir les propriétés en tant que virtual, puis remplacer l'implémentation dans une classe testable dérivée.

Russ Cam
la source
1
Je pense que le nom officiel du modèle est "Méthode du modèle".
davidjnelson
6

Je préfère l'injection de constructeur car elle aide à "appliquer" les exigences de dépendance d'une classe. Si c'est dans le c'tor, un consommateur doit définir les objets pour obtenir l'application à compiler. Si vous utilisez l'injection de setter, ils peuvent ne pas savoir qu'ils ont un problème avant l'exécution - et selon l'objet, il peut être en retard dans l'exécution.

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

ctacke
la source
6

Je préfère l'injection de constructeur, car cela semble le plus logique. C'est comme dire que ma classe a besoin de 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é pour définir des éléments auxquels le conteneur ne fait pas référence, comme une vue ASP.NET sur un présentateur créé à l'aide du conteneur.

Je ne pense pas que cela casse l'encapsulation. Le fonctionnement interne doit rester interne et les dépendances traitent d'une autre préoccupation.

David Kiff
la source
Merci pour votre réponse. Il semble certainement que le constructeur soit la réponse populaire. Cependant, je pense que cela rompt l'encapsulation d'une certaine manière. Injection de pré-dépendance, la classe déclarerait et instancierait tous les types concrets dont elle avait besoin pour faire son travail. Avec DI, les sous-classes (et tous les instanciateurs manuels) savent désormais quels outils une classe de base utilise. Vous ajoutez une nouvelle dépendance et maintenant vous devez chaîner l'instance à partir de toutes les sous-classes, même si elles n'ont pas besoin d'utiliser la dépendance elles-mêmes.
Niall Connaughton
Je viens d'écrire une belle et longue réponse et je l'ai perdue à cause d'une exception sur ce site !! :( En résumé, la classe de base est généralement utilisée pour réutiliser la logique. Cette logique pourrait facilement entrer dans la sous-classe ... donc vous pourriez penser à la classe de base et à la sous-classe = une préoccupation, qui dépend de plusieurs objets externes, qui faire des travaux différents. Le fait que vous ayez des dépendances, ne signifie pas que vous devez exposer tout ce que vous auriez précédemment gardé privé.
David Kiff
3

Une option qui pourrait valoir la peine d'être envisagée consiste à composer des dépendances multiples complexes à partir de simples dépendances uniques. Autrement dit, définissez des classes supplémentaires pour les dépendances composées. Cela facilite un peu l'injection du constructeur WRT - moins de paramètres par appel - tout en conservant la chose qui doit fournir toutes les dépendances à instancier.

Bien sûr, cela a plus de sens s'il existe une sorte de regroupement logique de 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 existent 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 / paramètres de propriété pour spécifier les dépendances, les options, etc. Les noms d'appel aident à décrire ce qui se passe. C'est une bonne idée de fournir des exemples d'extraits de code this-is-how-to-set-it-up, cependant, et assurez-vous 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.

Steve314
la source
3

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 de constructeur. De cette façon, la classe conserve son comportement par défaut à moins qu'elle ne soit remplacée par le consommateur de la classe.

L'utilisation de paramètres facultatifs ne peut être effectuée que dans les frameworks 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é fournie 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 allez devoir refactoriser votre constructeur et tout le code qui consomme 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 tout en offrant la possibilité de remplacer l'implémentation par défaut si nécessaire.

Ben McCormack
la source
1

Ceci est un ancien article, mais s'il est nécessaire à l'avenir, peut-être que c'est utile:

https://github.com/omegamit6zeichen/prinject

J'ai eu une idée similaire et j'ai proposé ce cadre. C'est probablement loin d'être complet, mais c'est une idée de cadre axé sur l'injection de propriété

Markus
la source
0

Cela dépend de la manière 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 structure de la société est compatible avec le serveur oracle, je vais configurer mes valeurs de source de données pour un bean acheminant des connexions via l'injection du constructeur. Sinon, si mon application est un produit et qu'elle peut se connecter à n'importe quelle base de données du client, je mettrais en œuvre une telle configuration de base de données et une implémentation multimarque 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.

ramarao gangina
la source
1
Même lors du codage en interne, je code toujours du point de vue que je suis un entrepreneur indépendant développant du code destiné à être redistribuable. Je suppose que ça va être open source. De cette façon, je m'assure que le code est modulaire et enfichable et suit les principes SOLID.
Fred
0

L'injection de constructeur révèle explicitement les dépendances, rendant 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 à se balancer dans un sens ou dans l'autre selon le projet. Personnellement, j'ai des problèmes avec le code qui sent 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é,

Choisissez judicieusement l'injection par constructeur ou par propriété en fonction de votre scénario spécifique. Et ne pensez pas que vous devez utiliser la 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 les avantages. Rester simple.

David Spenard
la source