web-dev-qa-db-fra.com

L'injection de dépendance doit-elle se faire au détriment de l'encapsulation?

Si je comprends bien, le mécanisme typique de l'injection de dépendance consiste à injecter via le constructeur d'une classe ou une propriété publique (membre) de la classe.

Cela expose la dépendance en cours d’injection et viole le principe d’encapsulation OOP.

Ai-je raison d'identifier ce compromis? Comment gérez-vous ce problème?

S'il vous plaît voir également ma réponse à ma propre question ci-dessous.

117
urig

Il existe une autre façon d’examiner cette question qui pourrait vous intéresser.

Lorsque nous utilisons l'injection IoC/dependency, nous n'utilisons pas les concepts OOP. Certes, nous utilisons un langage OO en tant qu '"hôte", mais les idées à la base d'IoC proviennent d'un génie logiciel orienté composants, et non de OO.

Le logiciel de composant concerne uniquement la gestion des dépendances - un exemple couramment utilisé est le mécanisme d'assemblage de .NET. Chaque assemblage publie la liste des assemblys auxquels il fait référence, ce qui facilite grandement la compilation (et la validation) des éléments nécessaires à l'exécution d'une application.

En appliquant des techniques similaires dans nos OO via IoC, nous souhaitons faciliter la configuration et la maintenance des programmes. La publication de 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é composant/service, il n'y a pas de «type d'implémentation» pour les détails.

Malheureusement, nos langues ne séparent pas les concepts orientés objet à grain fin des concepts orientés composant à grain plus grossier, c'est donc une distinction que vous ne devez garder en tête que :)

59
Nicholas Blumhardt

C'est une bonne question - mais à un moment donné, l'encapsulation dans sa forme la plus pure doit être violée si l'objet doit avoir sa dépendance remplie. Certains fournisseurs de la dépendance must savent tous les deux que l’objet en question requiert un Fooet que le fournisseur doit pouvoir fournir le Fooà l’objet.

Classiquement, ce dernier cas est traité comme vous le dites, à travers 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 que la dépendance sera définie via la réflexion sans que vous ayez besoin de l'exposer. à travers 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 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 jouer leur rôle (c'est-à-dire être dans un état valide) soit de toute façon fourni par le constructeur. Si vous avez un objet qui nécessite un collaborateur, il me semble bien que le constructeur publie publiquement cette exigence et veille à ce qu'il soit rempli lors de la création d'une nouvelle instance de la classe.

Idéalement, lorsque vous manipulez des objets, vous interagissez avec eux via une interface. Plus vous le faites (et vous avez des dépendances câblées via DI), moins vous avez réellement à traiter avec les constructeurs. Dans l'idéal, votre code ne traite pas et ne crée même jamais d'instances concrètes de classes. ainsi, il reçoit simplement un IFoovia DI, sans se soucier de ce que le constructeur de FooImplindique qu'il doit faire son travail, et en fait sans même connaître l'existence de FooImplname__. De ce point de vue, l’encapsulation est parfaite.

Ceci est une opinion bien sûr, mais à mon sens, DI n’enfreint pas nécessairement l’encapsulation et peut en fait l’aider en centralisant toutes les connaissances nécessaires sur les composants internes. 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 des codes que vous écrivez n'a besoin de connaître les dépendances des classes.

29
Andrzej Doyle

Cela expose la dépendance en cours d’injection et viole le principe d’encapsulation OOP.

Eh bien, franchement, tout viole l’encapsulation. :) C'est une sorte de principe d'appel d'offres qui doit être bien traité.

Alors, qu'est-ce qui viole l'encapsulation?

Héritage fait .

"Parce que l'héritage expose une sous-classe aux détails de la mise en œuvre de son parent, on dit souvent que" l'héritage rompt l'encapsulation "". (Gang of Four 1995: 19)

Programmation orientée aspect fait . Par exemple, vous enregistrez un rappel onMethodCall (), ce qui vous donne une excellente occasion d'injecter du code dans l'évaluation de méthode normale, en ajoutant des effets secondaires étranges, etc.

Déclaration d'ami en C++ fait .

Extension de classe en Ruby fait . Il suffit de redéfinir 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 fait .

L'encapsulation est un principe bon et important. Mais pas le seul.

switch (principle)
{
      case encapsulation:
           if (there_is_a_reason)
      break!
}
17
topright gamedev

Oui, DI enfreint l'encapsulation (également appelée "masquage d'informations").

Mais le vrai problème vient lorsque les développeurs l'utilisent comme excuse pour violer les principes KISS (Keep It Short and Simple) et YAGNI (Vous n'en aurez pas besoin).

Personnellement, je préfère des solutions simples et efficaces. J'utilise principalement le "nouvel" opérateur pour instancier des dépendances avec état chaque fois que nécessaire. Il est simple, bien encapsulé, facile à comprendre et à tester. Alors pourquoi pas?

13
Rogério

Un bon système/conteneur d’injection de dépendance permettra l’injection par le constructeur. Les objets dépendants seront encapsulés et ne devront pas du tout être exposés publiquement. De plus, en utilisant un système DP, aucun de vos codes ne "sait" même les détails de la construction de l’objet, y compris éventuellement l’objet en cours de construction. Il y a plus d'encapsulation dans ce cas puisque presque tout votre code est non seulement protégé 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, le plus souvent dans son constructeur. D'après ce que je comprends de DP, nous souhaitons soustraire cette responsabilité à l'objet et la confier à quelqu'un d'autre. À cette fin, le "quelqu'un d'autre", qui est le conteneur DP en l'espèce, possède une connaissance intime qui "viole" l'encapsulation; L'avantage est qu'il tire cette connaissance de l'objet, elle-même. Quelqu'un doit l'avoir. Le reste de votre application ne le fait pas.

J'y penserais de la façon suivante: le conteneur/système d'injection de dépendance enfreint l'encapsulation, mais votre code ne le fait pas. En fait, votre code est plus "encapsulé" que jamais.

5
Bill

Comme Jeff Sternal l'a souligné dans un commentaire sur la question, la réponse dépend entièrement de la définition de encapsulation .

Il semble y avoir deux camps principaux de ce que signifie encapsulation:

  1. Tout ce qui concerne l'objet est une méthode sur un objet. Ainsi, un objet File peut avoir des méthodes en Save, Print, Display, ModifyText, etc.
  2. Un objet est son propre petit monde et ne dépend pas d'un comportement extérieur.

Ces deux définitions sont en contradiction directe l'une avec l'autre. Si un objet File peut s’imprimer lui-même, cela dépendra beaucoup du comportement de l’imprimante. D'autre part, si simplement sait à propos de quelque chose qui peut imprimer pour elle (une IFilePrinter ou une interface similaire), alors l'objet File ne doit rien savoir de l'impression, et travailler avec elle apportera moins de dépendances dans l'objet.

Ainsi, l’injection de dépendance rompra 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 n’a clairement pas d’échelle (si c’était le cas, MS Word serait une grande classe).

D'autre part, l'injection de dépendance est presque obligatoire si vous utilisez la deuxième définition de l'encapsulation.

4
kyoryu

Cela ne viole pas l'encapsulation. Vous fournissez un collaborateur, mais la classe décide de son utilisation. Tant que vous suivez Dites, ne demandez pas tout va bien. Je trouve que l'injection par le constructeur est préférable, mais les préparateurs peuvent être efficaces aussi longtemps qu'ils sont intelligents. C'est-à-dire qu'ils contiennent une logique pour maintenir les invariants représentés par la classe.

4
Jason Watkins

Ceci est similaire à la réponse votée, mais je veux réfléchir à voix haute - peut-être que d'autres voient les choses de cette façon aussi.

  • Classical OO utilise des constructeurs pour définir le contrat public "d'initialisation" pour les consommateurs de la classe (masquant TOUS les détails de la mise en oeuvre; autrement dit, encapsulation). Ce contrat peut garantir qu’après l’instanciation, vous disposez d’un objet prêt à l’emploi (c’est-à-dire qu’aucune étape supplémentaire d’initialisation ne doit être mémorisée (par exemple, par l’oubli) par l’utilisateur).

  • (constructeur) DI rompt indéniablement l’encapsulation en saignant détail de l’implémentation à travers cette interface publique du constructeur. 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 violation horrible de l'encapsulation.

Exemple théorique:

La classe Foo a 4 méthodes et a besoin d’un nombre entier pour l’initialisation. Son constructeur ressemble donc à Foo (int size) et il est immédiatement clair pour les utilisateurs de la classe Foo qu’ils doivent fournir un taille à l’instanciation pour que Foo fonctionne.

Disons que cette implémentation particulière de Foo peut également nécessiter 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éplaît, c’est que nous avons maintenant un constructeur qui utilise blending données d’initialisation avec dépendances - une entrée est intéressante pour l’utilisateur de la classe ( size ), l’autre est une dépendance interne qui sert uniquement à confondre l'utilisateur et constitue un détail de la mise en œuvre ( widget ). 

Le paramètre size n'est PAS une dépendance. Il s'agit simplement d'une valeur d'initialisation par instance. IoC est dandy pour les dépendances externes (comme les widgets) mais pas pour l'initialisation d'état interne.

Pire encore, que faire 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 doit pas être utilisé!

Comment faire un compromis/réconcilier cela? 

Une approche consiste à basculer exclusivement vers des interfaces pour définir le contrat d'exploitation; et d'abolir l'utilisation de constructeurs par les utilisateurs. Pour être cohérent, tous les objets devraient être accessibles via des interfaces et instanciés uniquement via une forme de résolveur (comme un conteneur IOC/DI). Seul le conteneur peut instancier des choses. 

Cela prend en charge la dépendance du widget, mais comment initialiser la "taille" sans recourir à une méthode d'initialisation séparée 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 l'obtenez. Bummer, parce que j'aime beaucoup le idée et simplicité de l'injection du constructeur.

Comment obtenir une initialisation garantie dans ce monde DI, lorsque l'initialisation est supérieure à SEULEMENT les dépendances externes?

4
shawnT

L'encapsulation pure est un idéal qui ne peut jamais être atteint. Si toutes les dépendances étaient cachées, vous n’auriez plus besoin de DI. Pensez-y de cette façon, si vous avez vraiment des valeurs privées pouvant être internalisées dans l'objet, par exemple la valeur entière de la vitesse d'un objet voiture, vous n'avez alors aucune dépendance externe ni besoin d'inverser ou d'injecter cette dépendance. Ce type de valeurs d’état interne qui sont exploitées uniquement par des fonctions privées correspond à ce que vous souhaitez 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, le nouveau GMOverHeadCamEngine () - en interne dans le constructeur de l'objet car, préservant ainsi l'encapsulation, mais en créant un couplage beaucoup plus insidieux avec une classe concrète GMOverHeadCamEngine - ou 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, ce n’est pas l’essentiel - c’est que vous avez une voiture qui peut utiliser de nombreux types de moteurs sans être couplé à aucun d’eux, donc rendre votre base de code plus flexible et moins sujette aux effets secondaires. 

DI n’est pas une violation de l’encapsulation, mais un moyen de minimiser le couplage lorsque l’encapsulation est forcément interrompue dans pratiquement tous les OOP projets. L'injection externe d'une dépendance dans une interface minimise les effets secondaires du couplage et permet à vos classes de rester agnostiques face à la mise en œuvre. 

3
Dave Sims

Je crois en la simplicité. L’application de l’injection IOC/Dependecy dans les classes de domaine n’apporte aucune amélioration, sauf qu’il est beaucoup plus difficile de coder le code en ayant un fichier xml externe décrivant la relation. Beaucoup de technologies comme EJB 1.0/2.0 & struts 1.1 reviennent en arrière en réduisant le contenu de XML et en essayant de le mettre en code comme annotation, etc. Ainsi, appliquer IOC pour toutes les classes que vous avez développées créera le code. absurdité.

IOC a des avantages lorsque l'objet dépendant n'est pas prêt pour la création au moment de la compilation. Cela peut arriver 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 de IOC a plus de sens. Cela ne rend toutefois pas le code plus simple/maintenable.

Comme toutes les autres technologies, cela a aussi des avantages et des inconvénients. Ce qui me préoccupe, c’est que nous mettons en œuvre les dernières technologies dans tous les lieux, indépendamment de la meilleure utilisation de leur contexte.

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 connaître d'une manière ou d'une autre. Une chose 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 d'autre et doit not être injecté. Le fait que le cache soit utilisé est un détail d'implémentation que les clients de votre classe ne devraient pas avoir à se préoccuper.

Si votre classe a besoin de générer des flux de données, il est probablement logique d’injecter le flux de sortie afin que la classe puisse facilement exporter les résultats dans un tableau, un fichier ou à tout autre endroit où une autre personne pourrait vouloir envoyer les données.

Pour une zone grise, supposons que vous ayez une classe qui effectue une simulation de monte carlo. Cela nécessite une source de hasard. D'une part, le fait qu'il ait besoin de cela est un détail d'implémentation dans la mesure où le client ne se soucie vraiment pas de savoir d'où vient le caractère aléatoire. D'autre part, é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 que le client peut vouloir contrôler l'ensemencement pour obtenir un comportement répétable, l'injection peut avoir un sens. Dans ce cas, je suggérerais de proposer un moyen de créer la classe sans spécifier de générateur de nombres aléatoires, et d'utiliser un Singleton avec un thread local par défaut. Si/lorsque le besoin d'un contrôle plus précis se fait sentir, fournissez un autre constructeur permettant d'injecter une source de caractère aléatoire.

2
dsimcha

Ayant lutté un peu plus loin avec le problème, je suis maintenant d'avis que Dependency Injection enfreint (à ce moment-là) violer l'encapsulation dans une certaine mesure. Ne vous méprenez pas cependant - je pense que l'utilisation de l'injection de dépendance en vaut la peine dans la plupart des cas.

La raison pour laquelle DI entre en violation avec 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 que des sous-composants soient injectés via le constructeur (ou des propriétés publiques), il n'y a aucune garantie pour 

"empêchant les utilisateurs de définir les données internes du composant dans un état non valide ou incohérent".

Dans le même temps, on ne peut pas dire que 

"Les utilisateurs du composant (autres logiciels) ont seulement besoin de savoir ce qu’il fait et ne peuvent pas se rendre dépendant des détails de la procédure" .

Les deux citations sont de wikipedia .

Pour donner un exemple spécifique: je dois livrer un DLL côté client qui simplifie et masque la communication vers un service WCF (essentiellement une façade distante). Parce que cela dépend de 3 classes de proxy WCF différentes, si je suis 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 je cherche à cacher. 

En général, je suis tout pour DI. Dans cet exemple particulier (extrême), cela me semble dangereux. 

2
urig

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 de l'implémentation), puis utilise la classe (ce qui n'exige pas la connaissance de ces détails). Je vais expliquer pourquoi, mais d'abord une anaologie rapide de la voiture:

Lorsque je conduisais mon ancien Kombi de 1971, , Je pouvais appuyer sur l'accélérateur et Serait (légèrement) plus rapide. Je n'avais pas besoin de savoir pourquoi, mais ceux qui Ont construit le Kombi à l'usine savaient Pourquoi.

Mais revenons au codage. Encapsulation "masque un détail d'implémentation à quelque chose utilisant cette implémentation". L'encapsulation est une bonne chose car les détails de l'implémentation peuvent changer sans que l'utilisateur de la classe ne le sache.

Lors de l'utilisation de l'injection de dépendance, l'injection du constructeur est utilisée pour construire les objets service type (par opposition aux objets entité/valeur dont l'état du modèle). Toute variable membre dans l'objet de type de service représente des détails d'implémentation qui ne doivent pas fuir. par exemple. numéro de port du socket, informations d'identification de la base de données, une autre classe à appeler pour effectuer le cryptage, 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) relie ensemble tous les objets de service. Le conteneur DI ne connaît que les détails de la mise en œuvre. Il connaît tous les détails de la mise en œuvre, tout comme les gars de l'usine Kombi connaissent les bougies d'allumage.

Au moment de l'exécution, l'objet de service créé est appelé pour effectuer un travail réel. À ce moment, l'appelant de l'objet ne sait rien des détails de la mise en œuvre. 

C'est moi conduisant mon Kombi à la plage.

Revenons maintenant à l'encapsulation. Si les détails de l'implémentation changent, la classe utilisant cette implémentation au moment de l'exécution n'a pas besoin de changer. L'encapsulation n'est pas cassée. 

Je peux aussi conduire ma nouvelle voiture à la plage. L'encapsulation n'est pas cassée.

Si les détails de l'implémentation changent, le conteneur DI (ou l'usine) doit être changé. Vous n'avez jamais essayé de cacher les détails de la mise en œuvre à l'usine.

2
WW.

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 en tant que paramètre? Cela signifie-t-il que définir un entier en dehors du nouvel objet et l'introduire dans l'objet rompt l'encapsulation? Si le service est uniquement utilisé dans le nouvel objet, je ne vois pas en quoi cela romprait l’encapsulation.

En outre, en utilisant une sorte de fonctionnalité de transfert automatique (Autofac pour C #, par exemple), le code est extrêmement propre. En construisant des méthodes d'extension pour le constructeur Autofac, j'ai pu supprimer beaucoup de code de configuration DI que j'aurais dû gérer au fil du temps, à mesure que la liste des dépendances s'allongerait.

1
blooware

DI enfreint l'encapsulation pour les objets non partagés - point. 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 à l’objet créé doivent être COMPOSÉS dans l’objet créé - lorsque l’objet créé est détruit, il contient l’objet composé avec lui. Prenons comme exemple le corps humain. Ce qui est composé et ce qui est agrégé. Si nous utilisions DI, le constructeur du corps humain aurait une centaine 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 qu'il soit nécessaire de recourir à des influences externes (autres que les protéines). Ainsi, les cellules sanguines sont créées de manière interne par le corps - nouvelle BloodCell ().

Les avocats de DI soutiennent qu’un objet ne devrait JAMAIS utiliser le nouvel opérateur. Cette approche "puriste" viole non seulement l’encapsulation, mais également le principe de substitution de Liskov pour celui qui crée l’objet.

1
Greg Brand

J'ai eu du mal avec cette notion aussi. Au début, la «nécessité» d'utiliser le conteneur DI (comme Spring) pour instancier un objet ressenti comme si on sautait à 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 des objets dont j'ai besoin. Bien sûr, l’encapsulation est «brisée», car une personne «extérieure à la classe» sait ce dont elle 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 parce que DI 'sait' qu'un objet en a besoin d'un autre.

En fait, la situation 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 l'ID est impliqué du tout! Cela me remet le couvercle sur l’encapsulation. Ouf!

1
n8wrl

PS En fournissant Injection de dépendance vous ne faites pas nécessairement pause Encapsulation . Exemple:

obj.inject_dependency(  factory.get_instance_of_unknown_class(x)  );

Le code client ne connaît pas encore les détails de l'implémentation.

1
topright gamedev

Je pense qu'il va de soi que, à tout le moins, l'ID affaiblit considérablement l'encapsulation. En plus de cela, voici quelques autres inconvénients de DI à considérer.

  1. 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 dans lequel le client doit en quelque sorte découvrir quelles sont les dépendances de ce composant et 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 d'origine ASP.

  2. 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 impossibles à obtenir avec une dépendance concrète résolue de manière statique.

  3. Cela peut rendre le code moins flexible en ce sens que vous aurez peut-être moins de choix quant à la manière dont vous voulez qu'il fonctionne. Toutes les classes ne doivent pas nécessairement posséder toutes leurs dépendances pendant toute la durée de vie de l'instance propriétaire. Cependant, avec de nombreuses implémentations DI, vous n'avez aucune autre option.

En gardant cela à l'esprit, je pense que la question la plus importante devient alors: " une dépendance particulière doit-elle être spécifiée de manière externe? ". En pratique, j’ai rarement trouvé nécessaire de créer une dépendance fournie par une source externe, uniquement pour prendre en charge les tests.

Lorsqu'une dépendance doit véritablement être fournie de manière externe, cela suggère normalement que la relation entre les objets est une collaboration plutôt qu'une dépendance interne, auquel cas l'objectif approprié est alors d'encapsuler each class plutôt que d'encapsuler. d'une classe à l'intérieur de l'autre.

D'après mon expérience, le principal problème en ce qui concerne l'utilisation de DI est que, que vous commenciez avec un framework d'application avec DI intégré ou que vous ajoutiez une prise en charge DI à votre base de code, pour une raison quelconque, les gens présument que, puisque vous avez une prise en charge DI, celle-ci doit être correcte. moyen d'instancier tout . Ils n'ont même jamais pris la peine de poser la question "cette dépendance doit-elle être spécifiée de manière externe?". Et pire, ils commencent également à forcer tout le monde à utiliser le support ID pour tout aussi.

En conséquence, votre base de code commence inexorablement à évoluer vers un état dans lequel la création d'une instance de tout élément de votre base de code nécessite une multitude de configurations de conteneur DI obtus, et le débogage de tout élément est deux fois plus difficile, car vous avez la charge de travail supplémentaire pour et où tout était instancié.

Donc, ma réponse à la question est la suivante. Utilisez DI où vous pouvez identifier un problème réel qu'il résout pour vous, que vous ne pouvez résoudre plus simplement autrement.

1
Neutrino

Il vaut probablement la peine de mentionner que Encapsulation dépend un peu de la perspective.

public class A { 
    private B b;

    public A() {
        this.b = new B();
    }
}


public class A { 
    private B b;

    public A(B b) {
        this.b = b;
    }
}

Du point de vue de quelqu'un travaillant sur la classe A, dans le deuxième exemple, A en sait beaucoup moins sur la nature de this.b

Attendu sans DI

new A()

contre

new A(new B())

La personne qui consulte ce code en sait plus sur la nature de A dans le deuxième exemple.

Avec DI, au moins toutes les connaissances qui ont fui se trouvent au même endroit.

0
Tom B

Je conviens que, à l'extrême, DI peut violer l'encapsulation. Habituellement, DI expose des dépendances qui n'ont jamais été réellement encapsulées. Voici un exemple simplifié emprunté à Les singletons sont des menteurs pathologiques de Miško Hevery :

Vous commencez avec un test de carte de crédit et écrivez un test unitaire simple.

@Test
public void creditCard_Charge()
{
    CreditCard c = new CreditCard("1234 5678 9012 3456", 5, 2008);
    c.charge(100);
}

Le mois prochain, vous recevrez une facture de 100 $. Pourquoi as-tu été accusé? Le test unitaire a affecté une base de données de production. En interne, la carte de crédit appelle Database.getInstance(). Refactoriser CreditCard pour qu'il prenne une DatabaseInterface 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 car la classe CreditCard a des effets secondaires visibles de l'extérieur. Si vous voulez tester CreditCard sans refactoring, vous pouvez certainement observer la dépendance.

@Before
public void setUp()
{
    Database.setInstance(new MockDatabase());
}

@After
public void tearDown()
{
    Database.resetInstance();
}

Je ne pense pas qu'il soit utile de se demander si exposer la base de données en tant que dépendance réduit l'encapsulation, car sa conception est bonne. Toutes les décisions de DI ne seront pas aussi simples. Cependant, aucune des autres réponses ne montre un contre-exemple.

0
Craig P. Motlin

Je pense que c'est une question de portée. Lorsque vous définissez l’encapsulation (sans indiquer comment), vous devez définir la fonctionnalité encapsulée.

  1. 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, par exemple, des clients, cela ne fait pas partie de la chose encapsulée: le tri rapide.

  2. 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, vous effectuez quelque chose de utile concrete , puis vous utilisez l’option 2 de manière répétée.

Voici l'implémentation de l'instance configurée:

<bean id="clientSorter" class="QuickSort">
   <property name="comparator">
      <bean class="ClientComparator"/>
   </property>
</bean>

Voici comment un autre code client l'utilise:

<bean id="clientService" class"...">
   <property name="sorter" ref="clientSorter"/>
</bean>

Il est encapsulé car, si vous modifiez l'implémentation (vous modifiez la définition du bean clientSorter), cela n'interrompt 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 pas rien sur son classeur.

0
helios