Si je comprends bien, le mécanisme typique pour l'injection de dépendances consiste à injecter soit via le constructeur d'une classe, soit via une propriété publique (membre) de la classe.
Cela expose la dépendance injectée et viole le principe d'encapsulation de la POO.
Ai-je raison d'identifier ce compromis? Comment gérez-vous ce problème?
Veuillez également voir ma réponse à ma propre question ci-dessous.
Réponses:
Il y a une autre façon d'aborder ce problème qui pourrait vous intéresser.
Lorsque nous utilisons l'injection d'IoC / de dépendances, nous n'utilisons pas de concepts POO. Certes, nous utilisons un langage OO comme «hôte», mais les idées derrière l'IoC viennent de l'ingénierie logicielle orientée composants, et non de l'OO.
Le logiciel de composants consiste à gérer les dépendances - un exemple couramment utilisé est le mécanisme d'assemblage de .NET. Chaque assembly publie la liste des assemblys auxquels il fait référence, ce qui facilite grandement le regroupement (et la validation) des éléments nécessaires à une application en cours d'exécution.
En appliquant des techniques similaires dans nos programmes OO via IoC, nous visons à rendre les programmes plus faciles à configurer et à maintenir. La publication des dépendances (en tant que paramètres de constructeur ou autre) en est un élément clé. L'encapsulation ne s'applique pas vraiment, car dans le monde orienté composants / services, il n'y a pas de «type d'implémentation» pour que les détails fuient.
Malheureusement, nos langages ne séparent actuellement pas les concepts orientés objet à granularité fine de ceux orientés composants plus grossiers, c'est donc une distinction que vous devez garder à l'esprit uniquement :)
la source
C'est une bonne question - mais à un moment donné, l'encapsulation dans sa forme la plus pure doit être violée si l'objet veut que sa dépendance soit remplie. Certains fournisseurs de la dépendance doivent savoir à la fois que l'objet en question nécessite un
Foo
, et que le fournisseur doit avoir un moyen de fournir leFoo
à l'objet.Classiquement, ce dernier cas est géré comme vous le dites, via des arguments de constructeur ou des méthodes de définition. Cependant, ce n'est pas nécessairement vrai - je sais que les dernières versions du framework Spring DI en Java, par exemple, vous permettent d'annoter des champs privés (par exemple avec
@Autowired
) et la dépendance sera définie par réflexion sans que vous ayez besoin d'exposer la dépendance via l'une des classes méthodes / constructeurs publics. C'est peut-être le genre de solution que vous recherchiez.Cela dit, je ne pense pas non plus que l'injection de constructeur soit vraiment un problème. J'ai toujours pensé que les objets devraient être pleinement valides après la construction, de sorte que tout ce dont ils ont besoin pour remplir leur rôle (c'est-à-dire être dans un état valide) devrait de toute façon être fourni par le constructeur. Si vous avez un objet qui nécessite un collaborateur pour fonctionner, il me semble bien que le constructeur annonce publiquement cette exigence et s'assure qu'elle est remplie lorsqu'une nouvelle instance de la classe est créée.
Idéalement, lorsque vous traitez avec des objets, vous interagissez avec eux via une interface de toute façon, et plus vous faites cela (et avez des dépendances câblées via DI), moins vous avez à traiter vous-même avec les constructeurs. Dans la situation idéale, votre code ne traite ni ne crée jamais d'instances concrètes de classes; donc on lui donne juste une
IFoo
DI via, sans se soucier de ce que le constructeur d 'FooImpl
indique dont il a besoin pour faire son travail, et en fait sans même être conscient de sonFooImpl
existence. De ce point de vue, l'encapsulation est parfaite.C'est une opinion bien sûr, mais à mon avis, la DI ne viole pas nécessairement l'encapsulation et peut en fait l'aider en centralisant toutes les connaissances nécessaires sur les internes en un seul endroit. Non seulement c'est une bonne chose en soi, mais encore mieux cet endroit est en dehors de votre propre base de code, donc aucun code que vous écrivez n'a besoin de connaître les dépendances des classes.
la source
Eh bien, franchement, tout viole l'encapsulation. :) C'est une sorte de principe tendre qui doit être bien traité.
Alors, qu'est-ce qui viole l'encapsulation?
L'héritage fait .
La programmation orientée aspect le fait . Par exemple, vous enregistrez le rappel onMethodCall () et cela vous donne une excellente opportunité d'injecter du code dans l'évaluation de la méthode normale, en ajoutant des effets secondaires étranges, etc.
La déclaration d'ami en C ++ le fait .
L'extension de classe dans Ruby le fait . Redéfinissez simplement une méthode de chaîne quelque part après la définition complète d'une classe de chaîne.
Eh bien, beaucoup de choses le font .
L'encapsulation est un bon et important principe. Mais pas le seul.
la source
Oui, DI viole l'encapsulation (également connue sous le nom de «masquage d'informations»).
Mais le vrai problème survient lorsque les développeurs l'utilisent comme excuse pour violer les principes de KISS (Keep It Short and Simple) et YAGNI (You Ain't Gonna Need It).
Personnellement, je préfère des solutions simples et efficaces. J'utilise principalement l'opérateur "new" pour instancier des dépendances avec état quand et où elles sont nécessaires. Il est simple, bien encapsulé, facile à comprendre et facile à tester. Alors pourquoi pas?
la source
Un bon conteneur / système d'injection de dépendance permettra l'injection du constructeur. Les objets dépendants seront encapsulés et n'ont pas du tout besoin d'être exposés publiquement. En outre, en utilisant un système DP, aucun de votre code ne "connaît" même les détails de la façon dont l'objet est construit, y compris peut-être même l'objet en cours de construction. Il y a plus d'encapsulation dans ce cas puisque la quasi-totalité de votre code est non seulement protégée de la connaissance des objets encapsulés, mais ne participe même pas à la construction des objets.
Maintenant, je suppose que vous comparez avec le cas où l'objet créé crée ses propres objets encapsulés, très probablement dans son constructeur. Ma compréhension de DP est que nous voulons retirer cette responsabilité de l'objet et la donner à quelqu'un d'autre. À cette fin, le «quelqu'un d'autre», qui est le conteneur DP dans ce cas, a une connaissance intime qui «viole» l'encapsulation; l'avantage est qu'il tire cette connaissance de l'objet, lui-même. Quelqu'un doit l'avoir. Le reste de votre application ne le fait pas.
Je penserais de cette façon: le conteneur / système d'injection de dépendance viole l'encapsulation, mais pas votre code. En fait, votre code est plus «encapsulé» que jamais.
la source
Comme Jeff Sternal l'a souligné dans un commentaire à la question, la réponse dépend entièrement de la façon dont vous définissez l' encapsulation .
Il semble y avoir deux principaux camps de ce que signifie l'encapsulation:
File
objet peut avoir des méthodes àSave
,Print
,Display
,ModifyText
, etc.Ces deux définitions sont en contradiction directe l'une avec l'autre. Si un
File
objet peut s'imprimer, cela dépendra fortement du comportement de l'imprimante. D'un autre côté, s'il connaît simplement quelque chose qui peut imprimer pour lui (uneIFilePrinter
ou une interface de ce type), alors l'File
objet n'a pas besoin de savoir quoi que ce soit sur l'impression, et donc travailler avec lui apportera moins de dépendances dans l'objet.Ainsi, l'injection de dépendances interrompra l'encapsulation si vous utilisez la première définition. Mais, franchement, je ne sais pas si j'aime la première définition - elle ne s'adapte clairement pas (si c'était le cas, MS Word serait une grande classe).
D'un autre côté, l'injection de dépendances est presque obligatoire si vous utilisez la deuxième définition de l'encapsulation.
la source
Cela ne viole pas l'encapsulation. Vous fournissez un collaborateur, mais la classe décide de son utilisation. Tant que vous suivez Tell, ne demandez pas , tout va bien. Je trouve que l'injection du constructeur est préférable, mais les setters peuvent être bien aussi longtemps qu'ils sont intelligents. C'est-à-dire qu'ils contiennent une logique pour maintenir les invariants que la classe représente.
la source
C'est similaire à la réponse votée mais je veux réfléchir à haute voix - peut-être que d'autres voient les choses de cette façon également.
Le OO classique utilise des constructeurs pour définir le contrat public "d'initialisation" pour les consommateurs de la classe (masquant TOUS les détails d'implémentation; aka l'encapsulation). Ce contrat peut garantir qu'après instanciation, vous disposez d'un objet prêt à l'emploi (c'est-à-dire qu'aucune étape d'initialisation supplémentaire ne doit être mémorisée (euh, oubliée) par l'utilisateur).
(constructeur) DI rompt indéniablement l'encapsulation en saignant les détails d'implémentation via cette interface de constructeur publique. Tant que nous considérons toujours le constructeur public responsable de la définition du contrat d'initialisation pour les utilisateurs, nous avons créé une horrible violation de l'encapsulation.
Exemple théorique:
La classe Foo a 4 méthodes et a besoin d'un entier pour l'initialisation, donc son constructeur ressemble à Foo (taille int) et il est immédiatement clair pour les utilisateurs de la classe Foo qu'ils doivent fournir une taille à l'instanciation pour que Foo fonctionne.
Disons que cette implémentation particulière de Foo peut également avoir besoin d'un IWidget pour faire son travail. L'injection de constructeur de cette dépendance nous ferait créer un constructeur comme Foo (int size, widget IWidget)
Ce qui me dérange à ce sujet, c'est que nous avons maintenant un constructeur qui mélange les données d'initialisation avec les dépendances - une entrée intéresse l'utilisateur de la classe ( taille ), l'autre est une dépendance interne qui ne sert qu'à confondre l'utilisateur et est une implémentation détail ( widget ).
Le paramètre size n'est PAS une dépendance - c'est simplement une valeur d'initialisation par instance. IoC est dandy pour les dépendances externes (comme le widget) mais pas pour l'initialisation de l'état interne.
Pire encore, que se passe-t-il si le widget n'est nécessaire que pour 2 des 4 méthodes de cette classe; Il se peut que je subisse une surcharge d'instanciation pour Widget même s'il ne peut pas être utilisé!
Comment compromettre / concilier cela?
Une approche consiste à passer exclusivement aux interfaces pour définir le contrat d'exploitation; et abolir l'utilisation des constructeurs par les utilisateurs. Pour être cohérent, tous les objets devraient être accessibles uniquement via des interfaces et instanciés uniquement via une forme de résolveur (comme un conteneur IOC / DI). Seul le conteneur peut instancier les choses.
Cela prend en charge la dépendance Widget, mais comment initialiser "size" sans recourir à une méthode d'initialisation distincte sur l'interface Foo? En utilisant cette solution, nous avons perdu la possibilité de nous assurer qu'une instance de Foo est complètement initialisée au moment où vous obtenez l'instance. Dommage, parce que j'aime vraiment l' idée et la simplicité de l'injection de constructeur.
Comment obtenir une initialisation garantie dans ce monde DI, lorsque l'initialisation est PLUS QUE SEULEMENT des dépendances externes?
la source
L'encapsulation pure est un idéal qui ne peut jamais être atteint. Si toutes les dépendances étaient masquées, vous n'auriez pas du tout besoin de DI. Pensez-y de cette façon, si vous avez vraiment des valeurs privées qui peuvent être internalisées dans l'objet, par exemple la valeur entière de la vitesse d'un objet voiture, alors vous n'avez pas de dépendance externe et pas besoin d'inverser ou d'injecter cette dépendance. Ces sortes de valeurs d'état internes qui sont exploitées uniquement par des fonctions privées sont ce que vous voulez toujours encapsuler.
Mais si vous construisez une voiture qui veut un certain type d'objet moteur, vous avez une dépendance externe. Vous pouvez soit instancier ce moteur - par exemple new GMOverHeadCamEngine () - en interne dans le constructeur de l'objet voiture, en préservant l'encapsulation mais en créant un couplage beaucoup plus insidieux à une classe concrète GMOverHeadCamEngine, ou vous pouvez l'injecter, permettant à votre objet Car de fonctionner de manière agnostique (et beaucoup plus robuste) sur par exemple une interface IEngine sans la dépendance concrète. Que vous utilisiez un conteneur IOC ou une simple DI pour y parvenir n'est pas le but - le fait est que vous avez une voiture qui peut utiliser de nombreux types de moteurs sans être couplée à aucun d'entre eux, rendant ainsi votre base de code plus flexible et moins sujet aux effets secondaires.
La DI n'est pas une violation de l'encapsulation, c'est un moyen de minimiser le couplage lorsque l'encapsulation est nécessairement interrompue naturellement dans pratiquement tous les projets POO. L'injection d'une dépendance dans une interface minimise les effets secondaires du couplage et permet à vos classes de rester indépendantes de l'implémentation.
la source
Cela dépend si la dépendance est vraiment un détail d'implémentation ou quelque chose que le client voudrait / aurait besoin de savoir d'une manière ou d'une autre. Une chose qui est pertinente est le niveau d'abstraction ciblé par la classe. Voici quelques exemples:
Si vous avez une méthode qui utilise la mise en cache sous le capot pour accélérer les appels, alors l'objet de cache doit être un Singleton ou quelque chose et ne doit pas être injecté. Le fait que le cache soit utilisé est un détail d'implémentation dont les clients de votre classe ne devraient pas avoir à se soucier.
Si votre classe a besoin de générer des flux de données, il est probablement judicieux d'injecter le flux de sortie afin que la classe puisse facilement afficher les résultats dans un tableau, un fichier ou partout où quelqu'un d'autre voudrait envoyer les données.
Pour une zone grise, disons que vous avez une classe qui fait une simulation de Monte Carlo. Il a besoin d'une source d'aléatoire. D'une part, le fait qu'il en ait besoin est un détail de mise en œuvre dans la mesure où le client ne se soucie vraiment pas exactement d'où vient le caractère aléatoire. D'un autre côté, étant donné que les générateurs de nombres aléatoires du monde réel font des compromis entre le degré d'aléatoire, la vitesse, etc. que le client peut vouloir contrôler, et le client peut vouloir contrôler l'amorçage pour obtenir un comportement reproductible, l'injection peut avoir un sens. Dans ce cas, je suggérerais d'offrir un moyen de créer la classe sans spécifier de générateur de nombres aléatoires et d'utiliser un Singleton local au thread par défaut. Si / quand le besoin d'un contrôle plus fin se fait sentir, fournissez un autre constructeur qui permet d'injecter une source d'aléatoire.
la source
Je crois à la simplicité. L'application de l'injection IOC / Dependecy dans les classes de domaine n'apporte aucune amélioration, sauf en rendant le code beaucoup plus difficile à gérer en ayant un fichier xml externe décrivant la relation. De nombreuses technologies comme EJB 1.0 / 2.0 et struts 1.1 font marche arrière en réduisant le contenu du XML et en essayant de les mettre dans le code sous forme d'annoation, etc. Donc, appliquer IOC pour toutes les classes que vous développez rendra le code insensé.
IOC présente des avantages lorsque l'objet dépendant n'est pas prêt pour la création au moment de la compilation. Cela peut se produire dans la plupart des composants d'architecture de niveau abstrait d'infrasture, en essayant d'établir un cadre de base commun qui peut devoir fonctionner pour différents scénarios. Dans ces endroits, l'utilisation d'IOC a plus de sens. Pourtant, cela ne rend pas le code plus simple / maintenable.
Comme toutes les autres technologies, cela a aussi des avantages et des inconvénients. Mon souci est que nous implémentons les dernières technologies dans tous les endroits, quelle que soit leur meilleure utilisation du contexte.
la source
L'encapsulation n'est interrompue que si une classe a à la fois la responsabilité de créer l'objet (ce qui nécessite la connaissance des détails d'implémentation) et utilise ensuite la classe (qui ne nécessite pas la connaissance de ces détails). Je vais vous expliquer pourquoi, mais d'abord une anaologie rapide de la voiture:
Mais revenons au codage. L'encapsulation «cache un détail d'implémentation de quelque chose utilisant cette implémentation». L'encapsulation est une bonne chose car les détails d'implémentation peuvent changer sans que l'utilisateur de la classe le sache.
Lors de l'utilisation de l'injection de dépendances, l'injection de constructeur est utilisée pour construire des objets de type service (par opposition aux objets entité / valeur dont l'état du modèle). Toutes les variables de membre dans l'objet de type de service représentent des détails d'implémentation qui ne doivent pas s'échapper. par exemple, le numéro de port du socket, les informations d'identification de la base de données, une autre classe à appeler pour effectuer le chiffrement, un cache, etc.
Le constructeur est pertinent lors de la création initiale de la classe. Cela se produit pendant la phase de construction pendant que votre conteneur DI (ou usine) connecte tous les objets de service. Le conteneur DI ne connaît que les détails d'implémentation. Il sait tout sur les détails de mise en œuvre comme les gars de l'usine Kombi connaissent les bougies d'allumage.
Au moment de l'exécution, l'objet de service qui a été créé est appelé apon pour effectuer un travail réel. À ce stade, l'appelant de l'objet ne sait rien des détails d'implémentation.
Maintenant, revenons à l'encapsulation. Si les détails de l'implémentation changent, la classe qui utilise cette implémentation au moment de l'exécution n'a pas besoin de changer. L'encapsulation n'est pas interrompue.
Si les détails d'implémentation changent, le conteneur DI (ou l'usine) doit changer. Vous n'avez jamais essayé de masquer les détails d'implémentation de l'usine en premier lieu.
la source
Ayant lutté un peu plus loin avec le problème, je suis maintenant d'avis que l'injection de dépendance viole (à l'heure actuelle) l'encapsulation dans une certaine mesure. Ne vous méprenez pas cependant - je pense que l'utilisation de l'injection de dépendances vaut bien le compromis dans la plupart des cas.
La raison pour laquelle DI viole l'encapsulation devient claire lorsque le composant sur lequel vous travaillez doit être livré à une partie "externe" (pensez à écrire une bibliothèque pour un client).
Lorsque mon composant nécessite l'injection de sous-composants via le constructeur (ou des propriétés publiques), il n'y a aucune garantie pour
En même temps, on ne peut pas dire que
Les deux citations proviennent de wikipedia .
Pour donner un exemple spécifique: je dois fournir une DLL côté client qui simplifie et masque la communication avec un service WCF (essentiellement une façade distante). Parce que cela dépend de 3 classes de proxy WCF différentes, si j'adopte l'approche DI, je suis obligé de les exposer via le constructeur. Avec cela, j'expose les éléments internes de ma couche de communication que j'essaye de cacher.
En général, je suis tout à fait pour DI. Dans cet exemple particulier (extrême), cela me paraît dangereux.
la source
DI viole l'encapsulation pour les objets NON partagés - point final. Les objets partagés ont une durée de vie en dehors de l'objet en cours de création et doivent donc être AGRÉGÉS dans l'objet en cours de création. Les objets qui sont privés de l'objet en cours de création doivent être COMPOSÉS dans l'objet créé - lorsque l'objet créé est détruit, il prend l'objet composé avec lui. Prenons l'exemple du corps humain. Ce qui est composé et ce qui est agrégé. Si nous devions utiliser DI, le constructeur du corps humain aurait des centaines d'objets. De nombreux organes, par exemple, sont (potentiellement) remplaçables. Mais, ils sont toujours composés dans le corps. Les cellules sanguines sont créées dans le corps (et détruites) tous les jours, sans avoir besoin d'influences externes (autres que les protéines). Ainsi, les cellules sanguines sont créées en interne par le corps - new BloodCell ().
Les défenseurs de DI soutiennent qu'un objet ne doit JAMAIS utiliser le nouvel opérateur. Cette approche "puriste" viole non seulement l'encapsulation mais aussi le principe de substitution de Liskov pour quiconque crée l'objet.
la source
J'ai également eu du mal avec cette notion. Au début, «l'exigence» d'utiliser le conteneur DI (comme Spring) pour instancier un objet ressemblait à un saut à travers des cerceaux. Mais en réalité, ce n'est vraiment pas un cerceau - c'est juste une autre façon «publiée» de créer les objets dont j'ai besoin. Bien sûr, l'encapsulation est «cassée» parce que quelqu'un «en dehors de la classe» sait ce dont il a besoin, mais ce n'est vraiment pas le reste du système qui le sait - c'est le conteneur DI. Rien de magique ne se passe différemment car DI «sait» qu'un objet en a besoin d'un autre.
En fait, cela s'améliore encore - en me concentrant sur les usines et les référentiels, je n'ai même pas besoin de savoir que DI est impliqué du tout! Cela me remet le couvercle sur l'encapsulation. Ouf!
la source
PS. En fournissant l' injection de dépendances, vous ne rompez pas nécessairement l' encapsulation . Exemple:
Le code client ne connaît toujours pas les détails d'implémentation.
la source
C'est peut-être une façon naïve d'y penser, mais quelle est la différence entre un constructeur qui prend un paramètre entier et un constructeur qui prend un service comme paramètre? Cela signifie-t-il que la définition d'un entier en dehors du nouvel objet et son introduction dans l'objet interrompt l'encapsulation? Si le service n'est utilisé que dans le nouvel objet, je ne vois pas comment cela briserait l'encapsulation.
De plus, en utilisant une sorte de fonction de câblage automatique (Autofac pour C #, par exemple), cela rend le code extrêmement propre. En créant des méthodes d'extension pour le constructeur Autofac, j'ai pu supprimer BEAUCOUP de code de configuration DI que j'aurais dû maintenir au fil du temps à mesure que la liste des dépendances s'allongeait.
la source
Je pense qu'il va de soi qu'à tout le moins DI affaiblit considérablement l'encapsulation. En plus de cela, voici quelques autres inconvénients de l'ID à considérer.
Cela rend le code plus difficile à réutiliser. Un module qu'un client peut utiliser sans avoir à fournir explicitement des dépendances est évidemment plus facile à utiliser qu'un module où le client doit d'une manière ou d'une autre découvrir quelles sont les dépendances de ce composant, puis les rendre disponibles. Par exemple, un composant créé à l'origine pour être utilisé dans une application ASP peut s'attendre à ce que ses dépendances soient fournies par un conteneur DI qui fournit aux instances d'objet des durées de vie liées aux demandes http du client. Cela peut ne pas être simple à reproduire dans un autre client qui ne vient pas avec le même conteneur DI intégré que l'application ASP d'origine.
Cela peut rendre le code plus fragile. Les dépendances fournies par la spécification d'interface peuvent être implémentées de manière inattendue, ce qui donne lieu à toute une classe de bogues d'exécution qui ne sont pas possibles avec une dépendance concrète résolue statiquement.
Cela peut rendre le code moins flexible dans le sens où vous risquez de vous retrouver avec moins de choix quant à la façon dont vous voulez qu'il fonctionne. Toutes les classes n'ont pas besoin d'exister toutes ses dépendances pendant toute la durée de vie de l'instance propriétaire, mais avec de nombreuses implémentations DI, vous n'avez pas d'autre option.
Dans cet esprit, je pense que la question la plus importante devient alors " une dépendance particulière doit-elle être spécifiée de l'extérieur? ". En pratique, j'ai rarement trouvé nécessaire de créer une dépendance fournie en externe uniquement pour prendre en charge les tests.
Lorsqu'une dépendance doit véritablement être fournie de l'extérieur, cela suggère normalement que la relation entre les objets est une collaboration plutôt qu'une dépendance interne, auquel cas le but approprié est alors l'encapsulation de chaque classe, plutôt que l'encapsulation d'une classe dans l'autre .
D'après mon expérience, le principal problème concernant l'utilisation de DI est que, que vous commenciez avec un framework d'application avec DI intégré, ou que vous ajoutiez le support DI à votre base de code, pour une raison quelconque, les gens supposent que puisque vous avez le support DI, cela doit être le bon façon de tout instancier . Ils ne se donnent même jamais la peine de se poser la question "cette dépendance doit-elle être spécifiée de l'extérieur?". Et pire, ils commencent également à essayer de forcer tout le monde à utiliser le support DI pour tout aussi.
Le résultat de ceci est que inexorablement votre base de code commence à évoluer dans un état où la création de toute instance de quoi que ce soit dans votre base de code nécessite des rames de configuration de conteneur DI obtuse, et le débogage de quoi que ce soit est deux fois plus difficile car vous avez la charge de travail supplémentaire d'essayer d'identifier comment et où tout a été instancié.
Ma réponse à la question est donc la suivante. Utilisez DI où vous pouvez identifier un problème réel qu'il résout pour vous, que vous ne pouvez pas résoudre plus simplement d'une autre manière.
la source
Je suis d'accord que prise à l'extrême, DI peut violer l'encapsulation. Habituellement, DI expose des dépendances qui n'ont jamais été vraiment encapsulées. Voici un exemple simplifié emprunté à Singletons are Pathological Liars de Miško Hevery :
Vous commencez par un test de carte de crédit et écrivez un test unitaire simple.
Le mois prochain, vous recevrez une facture de 100 $. Pourquoi avez-vous été facturé? Le test unitaire a affecté une base de données de production. En interne, les appels par carte de crédit
Database.getInstance()
. Refactoriser CreditCard afin qu'il prenne unDatabaseInterface
dans son constructeur expose le fait qu'il existe une dépendance. Mais je dirais que la dépendance n'a jamais été encapsulée au départ car la classe CreditCard provoque des effets secondaires visibles de l'extérieur. Si vous souhaitez tester CreditCard sans refactoring, vous pouvez certainement observer la dépendance.Je ne pense pas que cela vaille la peine de se demander si exposer la base de données en tant que dépendance réduit l'encapsulation, car c'est une bonne conception. Toutes les décisions de DI ne seront pas aussi simples. Cependant, aucune des autres réponses ne montre un contre-exemple.
la source
CreditCard
objet peut-il passer d'une WebAPI à PayPal, sans que rien d'extérieur à la classe ne doive changer?Je pense que c'est une question de portée. Lorsque vous définissez l'encapsulation (sans savoir comment), vous devez définir quelle est la fonctionnalité encapsulée.
Classe telle quelle: ce que vous encapsulez est la seule responsabilité de la classe. Ce qu'il sait faire. Par exemple, le tri. Si vous injectez un comparateur pour commander, disons, des clients, cela ne fait pas partie de la chose encapsulée: le tri rapide.
Fonctionnalité configurée : si vous souhaitez fournir une fonctionnalité prête à l'emploi, vous ne fournissez pas la classe QuickSort, mais une instance de la classe QuickSort configurée avec un comparateur. Dans ce cas, le code responsable de la création et de la configuration doit être masqué du code utilisateur. Et c'est l'encapsulation.
Lorsque vous programmez des classes, c'est-à-dire que vous implémentez des responsabilités uniques dans des classes, vous utilisez l'option 1.
Lorsque vous programmez des applications, c'est-à-dire que vous faites quelque chose qui entreprend un travail concret utile, vous utilisez à plusieurs reprises l'option 2.
Voici l'implémentation de l'instance configurée:
Voici comment un autre code client l'utilise:
Il est encapsulé car si vous modifiez l'implémentation (vous changez la
clientSorter
définition du bean), cela ne rompt pas l'utilisation du client. Peut-être que, lorsque vous utilisez des fichiers xml avec tous écrits ensemble, vous voyez tous les détails. Mais croyez-moi, le code client (ClientService
) ne sait rien de son trieur.la source
Cela vaut probablement la peine de mentionner que cela
Encapsulation
dépend quelque peu de la perspective.Du point de vue de quelqu'un travaillant sur la
A
classe, dans le deuxième exemple enA
sait beaucoup moins sur la naturethis.b
Alors que sans DI
contre
La personne qui regarde ce code en sait plus sur la nature de
A
dans le deuxième exemple.Avec DI, au moins toutes ces connaissances divulguées se trouvent au même endroit.
la source