Les conteneurs d'IOC enfreignent les principes de la POO

51

Quel est le but des conteneurs IOC? Les raisons combinées peuvent être simplifiées comme suit:

Lorsque vous utilisez les principes de développement OOP / SOLID, l’injection de dépendance est compliquée. Soit vous avez les points d’entrée de premier niveau qui gèrent les dépendances pour plusieurs niveaux en dessous d’eux-mêmes et les transmettent récursivement lors de la construction, ou vous avez un peu de code en double dans les modèles fabrique / constructeur et les interfaces qui construisent des dépendances selon vos besoins.

Il n’existe pas de solution OOP / SOLID pour effectuer cela ET d’ avoir un très beau code.

Si cette déclaration précédente est vraie, comment les conteneurs IOC le font-ils? Autant que je sache, ils n'utilisent pas une technique inconnue qui ne peut pas être réalisée avec une DI manuelle. La seule explication est que les conteneurs IOC enfreignent les principes de la POO / SOLID en utilisant des accesseurs privés d'objets statiques.

Les conteneurs IOC enfreignent-ils les principes suivants en coulisse? Telle est la véritable question, car je comprends bien, mais j'ai le sentiment que quelqu'un d'autre comprend mieux:

  1. Contrôle de la portée . La portée est la raison de presque toutes les décisions que je prends concernant la conception de mon code. Bloc, Local, Module, Statique / Global. La portée doit être très explicite, autant au niveau des blocs que de la statique autant que possible. Vous devriez voir les déclarations, les instanciations et les fins de cycle de vie. Je fais confiance au langage et au GC pour gérer la portée tant que je suis explicite avec elle. Dans le cadre de mes recherches, j'ai constaté que les conteneurs IOC configuraient la plupart ou toutes les dépendances en tant que statiques et les contrôlaient via une implémentation AOP en coulisse. Donc, rien n'est transparent.
  2. Encapsulation . Quel est le but de l'encapsulation? Pourquoi devrions-nous garder les membres privés ainsi? Pour des raisons pratiques, il est donc important que les développeurs de notre API ne puissent pas interrompre l'implémentation en modifiant l'état (qui devrait être géré par la classe de propriétaires). Mais aussi, pour des raisons de sécurité, il est impossible d'éviter les injections qui dépassent nos États membres et contournent la validation et le contrôle de classe. Donc, tout ce qui (les frameworks Mocking ou IOC) injecte du code avant la compilation pour permettre un accès externe à des membres privés est assez énorme.
  3. Principe de responsabilité unique . En surface, les conteneurs IOC semblent rendre les choses plus propres. Mais imaginez comment vous feriez la même chose sans les frameworks d'aide. Vous auriez des constructeurs avec une douzaine de dépendances passées. Cela ne veut pas dire que vous les recouvriez avec IOC Containers, c'est une bonne chose! C'est un signe de re-factoriser votre code et de suivre SRP.
  4. Ouvert / Fermé . Tout comme SRP n’est pas réservé aux classes (j’applique SRP aux lignes à responsabilité unique, sans parler des méthodes). Open / Closed n'est pas simplement une théorie de haut niveau pour ne pas modifier le code d'une classe. C'est une pratique de comprendre la configuration de votre programme et de contrôler ce qui est modifié et ce qui est étendu. Les conteneurs IOC peuvent modifier partiellement le mode de fonctionnement de vos classes pour les raisons suivantes:

    • une. Le code principal ne détermine pas la commutation des dépendances, mais la configuration du framework.

    • b. La portée peut être modifiée à un moment qui n'est pas contrôlé par les membres appelants, elle est plutôt déterminée en externe par un cadre statique.

La configuration de la classe n'est donc pas vraiment fermée, elle se modifie elle-même en fonction de la configuration d'un outil tiers.

La raison pour laquelle c'est une question est parce que je ne suis pas nécessairement un maître de tous les conteneurs IOC. Et bien que l’idée d’un conteneur IOC soit intéressante, elle apparaît simplement comme une façade recouvrant une mauvaise mise en œuvre. Mais pour accomplir le contrôle externe de la portée, l’accès des membres privés et le chargement paresseux, il reste encore beaucoup à faire. La procédure AOP est excellente, mais la manière dont elle est réalisée via IOC Containers est également discutable.

Je peux faire confiance à C # et au .NET GC pour faire ce que j’attends. Mais je ne peux pas faire autant confiance à un outil tiers qui modifie mon code compilé pour effectuer des solutions de contournement à des tâches que je ne serais pas en mesure de faire manuellement.

EG: Entity Framework et d'autres ORM créent des objets fortement typés et les mappent vers des entités de base de données, tout en fournissant une fonctionnalité de base de la plaque de la chaudière pour l'exécution de CRUD. N'importe qui peut créer son propre ORM et continuer à suivre les principes de la POO / SOLID manuellement. Mais ces cadres nous aident à ne pas réinventer la roue à chaque fois. Alors que IOC Containers, semble-t-il, nous aide à contourner volontairement les principes OOP / SOLID et à les dissimuler.

Suamere
la source
13
+1 La corrélation entre IOCet Explicitness of codeest exactement ce qui me pose problème. La DI manuelle peut facilement devenir une corvée, mais elle met au moins un flux de programme explicite autonome à un seul endroit: "Vous obtenez ce que vous voyez, vous voyez ce que vous obtenez". Alors que les reliures et les déclarations de conteneurs IOC peuvent facilement devenir un programme parallèle caché.
Eugene Podskal
6
Comment est-ce même une question?
Stephen
8
Cela ressemble plus à un article de blog qu'à une question.
Bart van Ingen Schenau le
4
J'ai une catégorie interne pour les questions, "trop ​​argumentative", ce qui n'est souvent pas une raison formelle mais que je vote, voire même voter, pour fermer "pas une vraie question". Il a cependant deux aspects et cette question ne répond qu'au premier. (1) La question énonce une thèse et demande si elle est correcte, (2) l'interlocuteur répond aux réponses qui ne sont pas d'accord, en défendant la position initiale. La réponse de Matthew contredit en partie ce que dit la question. Par conséquent, à moins que le questionneur ne rampe partout, je le considère personnellement comme une question de bonne foi, même si elle a la forme suivante: "J'ai raison, n'est-ce pas?"
Steve Jessop
3
@ BenAaronson Merci Ben. J'essaie de communiquer ce que j'ai appris dans le cadre de mes recherches et d'obtenir des réponses sur la nature des conteneurs IOC. À la suite de mes recherches, j'ai trouvé que ces principes étaient brisés. La meilleure réponse à ma question serait de confirmer ou de nier que IOC Containers puisse faire des choses que nous ne pouvons pas faire manuellement sans enfreindre les principes OOP / SOLID. En raison de vos excellents commentaires, j'ai reformulé ma question avec plus d'exemples.
Suamere

Réponses:

35

Je vais passer en revue vos points numériquement, mais d’abord, il ya une chose à laquelle vous devriez faire très attention: ne confondez pas la façon dont un consommateur utilise une bibliothèque avec la façon dont la bibliothèque est mise en œuvre . Entity Framework (que vous citez vous-même comme une bonne bibliothèque) et MVC d'ASP.NET en sont de bons exemples. Les deux font beaucoup de choses sous le capot avec, par exemple, une réflexion, ce qui ne serait absolument pas considéré comme un bon design si vous le diffusiez dans le code quotidien.

Ces bibliothèques ne sont absolument pas "transparentes" dans leur fonctionnement ou ce qu'elles font en coulisse. Mais cela n’est pas préjudiciable, car ils appuient de bons principes de programmation chez leurs consommateurs . Ainsi, chaque fois que vous parlez d'une bibliothèque comme celle-ci, souvenez-vous qu'en tant que consommateur d'une bibliothèque, vous n'avez pas à vous soucier de son implémentation ou de sa maintenance. Vous ne devez vous soucier que de la manière dont cela aide ou gêne le code que vous écrivez et qui utilise la bibliothèque. Ne confondez pas ces concepts!

Donc, pour passer par point:

  1. Nous arrivons immédiatement à ce que je peux seulement supposer être un exemple de ce qui précède. Vous dites que les conteneurs IOC configurent la plupart des dépendances de manière statique. Eh bien, peut-être que certains détails d’implémentation de leur fonctionnement incluent le stockage statique (bien que, étant donné qu’ils ont tendance à avoir une instance d’un objet comme Ninject IKernelcomme stockage principal, je doute même de cela). Mais ce n'est pas votre préoccupation!

    En fait, un conteneur IoC est tout aussi explicite et complet que l'injection de dépendance du pauvre. (Je vais continuer à comparer les conteneurs IoC aux DI du pauvre, car les comparer à aucune DI serait injuste et déroutant. Et, pour être clair, je n'utilise pas "le DI du pauvre" comme péjoratif.)

    Dans le DI du pauvre, vous instanciez une dépendance manuellement, puis l'injectez dans la classe qui en a besoin. Au moment où vous construisez la dépendance, vous choisissez quoi faire: stockez-la dans une variable localisée, une variable de classe, une variable statique, ne la stockez pas du tout, peu importe. Vous pouvez transmettre la même instance à un grand nombre de classes ou en créer une nouvelle pour chaque classe. Peu importe. Le fait est que, pour voir ce qui se passe, vous devez rechercher le point, probablement près de la racine de l'application, où la dépendance est créée.

    Maintenant, qu'en est-il un conteneur IoC? Eh bien, vous faites exactement la même chose! Encore une fois, en passant par la terminologie Ninject, vous regardez où la liaison est mis en place, et de trouver si elle dit quelque chose comme InTransientScope, InSingletonScopeou autre chose. Si quelque chose est potentiellement plus clair, parce que vous avez du code déclarant sa portée, plutôt que de devoir regarder à travers une méthode pour suivre ce qui arrive à un objet (un objet peut être limité à un bloc, mais est-il utilisé plusieurs fois dans cette bloquer ou juste une fois, par exemple). Alors peut-être que l'idée de devoir définir une portée sur le conteneur IoC plutôt que sur une langue brute vous rebute, mais que vous ayez confiance en votre bibliothèque IoC - ce que vous devriez faire! - cela ne présente pas de réel inconvénient. .

  2. Je ne sais toujours pas vraiment de quoi vous parlez ici. Les conteneurs IoC considèrent-ils les propriétés privées dans le cadre de leur mise en œuvre interne? Je ne sais pas pourquoi ils le feraient, mais encore une fois, s'ils le font, la façon dont une bibliothèque que vous utilisez est mise en œuvre ne vous concerne pas.

    Ou peut-être exposent-ils une capacité telle que l'injection dans des setters privés? Honnêtement, je n'ai jamais rencontré cela, et je doute que ce soit une caractéristique commune. Mais même s’il existe, c’est un simple cas d’outil qui peut être mal utilisé. N'oubliez pas que même sans conteneur IoC, ce ne sont que quelques lignes de code Reflection permettant d'accéder à une propriété privée et de la modifier. C'est quelque chose que vous ne devriez presque jamais faire, mais cela ne veut pas dire que .NET est mauvais pour exposer cette capacité. Si quelqu'un abuse si manifestement et si sauvagement d'un outil, c'est sa faute, pas son outil.

  3. Le point ultime ici est similaire à 2. La plupart du temps, les conteneurs IoC utilisent le constructeur! L'injection de poseuse est proposée dans des cas très spécifiques où, pour des raisons particulières, l'injection de constructeur ne peut pas être utilisée. Toute personne qui utilise l'injection de setter tout le temps pour masquer le nombre de dépendances transmises est totalement absurde de l'outil. Ce n'est PAS la faute de l'outil, c'est la leur.

    Maintenant, si c’était une erreur très facile à faire innocemment, et que les conteneurs IoC encouragent, alors d’accord, peut-être auriez-vous raison. Ce serait comme rendre chaque membre de ma classe public, puis blâmer les autres quand ils modifient des choses qu'ils ne devraient pas, non? Mais toute personne qui utilise l'injection de setter pour couvrir des violations du SRP ignore soit totalement les principes de conception de base, soit en ignore totalement. Il est déraisonnable d'en imputer la responsabilité au conteneur IoC.

    C'est particulièrement vrai parce que c'est aussi quelque chose que vous pouvez faire aussi facilement avec la DI du pauvre homme:

    var myObject 
       = new MyTerriblyLargeObject { DependencyA = new Thing(), DependencyB = new Widget(), Dependency C = new Repository(), ... };
    

    Donc, vraiment, cette inquiétude semble totalement orthogonale à l'utilisation ou non d'un conteneur IoC.

  4. Changer la façon dont vos classes fonctionnent ensemble n'est pas une violation d'OCP. Si c'était le cas, alors toute inversion de dépendance encouragerait une violation de l'OCP. Si c'était le cas, ils ne seraient pas tous les deux dans le même acronyme SOLID!

    En outre, ni les points a) ni b) n’ont rien à voir avec OCP. Je ne sais même pas comment répondre à ces questions en ce qui concerne OCP.

    La seule chose que je peux deviner, c’est que, selon vous, OCP est lié au fait que le comportement n’est pas modifié au moment de l’exécution ou à la source de contrôle du cycle de vie des dépendances dans le code. Ce n'est pas. OCP consiste à ne pas avoir à modifier le code existant lorsque des exigences sont ajoutées ou modifiées. Il s'agit d' écrire du code, pas de la manière dont le code que vous avez déjà écrit est collé (bien que, bien sûr, un couplage lâche soit un élément important de la réalisation de l'OCP).

Et une dernière chose que vous dites:

Mais je ne peux pas faire autant confiance à un outil tiers qui modifie mon code compilé pour effectuer des solutions de contournement à des tâches que je ne serais pas en mesure de faire manuellement.

Oui, vous pouvez . Vous n'avez absolument aucune raison de penser que ces outils, sur lesquels s'appuient un grand nombre de projets, sont plus bogués ou sujets à un comportement inattendu que toute autre bibliothèque tierce.

Addenda

Je viens de remarquer que vos paragraphes d'introduction pourraient également utiliser un adressage. Vous dites avec ironie que les conteneurs IoC "n'utilisent pas une technique secrète dont nous n'avons jamais entendu parler" pour éviter un code compliqué, sujet à la duplication, afin de créer un graphe de dépendance. Et vous avez tout à fait raison, ce qu’ils font, c’est vraiment résoudre ces problèmes avec les mêmes techniques de base que les programmeurs.

Permettez-moi de vous parler d'un scénario hypothétique. En tant que programmeur, vous créez une application volumineuse et, au point d’entrée, où vous construisez votre graphe d’objets, vous remarquez que le code est assez confus. Il y a pas mal de classes qui sont utilisées encore et encore, et chaque fois que vous en construisez une, vous devez reconstruire toute la chaîne de dépendances. De plus, vous ne disposez d'aucun moyen expressif de déclarer ou de contrôler le cycle de vie des dépendances, à l'exception d'un code personnalisé pour chacune d'entre elles. Votre code est non structuré et plein de répétition. C'est le désordre dont vous parlez dans votre paragraphe d'introduction.

Commençons donc par un peu de refactorisation, dans laquelle un code répété est suffisamment structuré pour être intégré dans des méthodes d'assistance, etc. Mais alors vous commencez à penser: est-ce un problème que je pourrais peut-être aborder de manière générale , un problème qui n'est pas spécifique à ce projet particulier mais qui pourrait vous aider dans tous vos projets futurs?

Alors, assoyez-vous et réfléchissez-y, et décidez qu'il devrait y avoir une classe capable de résoudre les dépendances. Et vous décrivez les méthodes publiques dont il aurait besoin:

void Bind(Type interfaceType, Type concreteType, bool singleton);
T Resolve<T>();

Binddit "où vous voyez un argument constructeur de type interfaceType, passez une instance de concreteType". Le singletonparamètre supplémentaire indique s'il faut utiliser la même instance à concreteTypechaque fois ou en créer une nouvelle.

Resolveessayera simplement de construire Tavec n'importe quel constructeur, il peut trouver dont les arguments sont tous des types qui ont été liés auparavant. Il peut également s'appeler de manière récursive pour résoudre les dépendances jusqu'au bout. S'il ne peut pas résoudre une instance parce que tout n'a pas été lié, il lève une exception.

Vous pouvez essayer de l'implémenter vous-même, et vous constaterez que vous avez besoin d'un peu de réflexion et d'une mise en cache pour les liaisons là où singletonc'est vrai, mais certainement rien de radical ou d'horrifiant. Et une fois que vous avez terminé, le tour est joué , vous avez le cœur de votre propre conteneur IoC! Est-ce vraiment si effrayant? La seule différence réelle entre ceci et Ninject ou StructureMap ou Castle Windsor ou celui que vous préférez est que ceux-ci ont beaucoup plus de fonctionnalités pour couvrir les (nombreux!) Cas d'utilisation où cette version de base ne serait pas suffisante. Mais au fond, ce que vous avez là est l’essence d’un conteneur IoC.

Ben Aaronson
la source
Merci Ben. En toute honnêteté, je ne travaille avec IOC Containers que depuis environ un an et, même si je ne le fais pas, les tutoriels et les pairs autour de moi sont vraiment axés sur l'injection de biens (y compris privés). C'est fou et ça soulève tous les points que je fais. Mais même si d’autres sont dans cette situation, je pense que la meilleure chose à faire est d’apprendre comment IOC Containers devrait s’engager à suivre les principes et de signaler ces rupteurs de principe lors de la révision du code. Cependant ... (suite)
Suamere
Ma plus grande préoccupation ne figure pas nécessairement dans les principes OOP / SOLID, c’est le caractère explicite de la portée. Nous sommes toujours en train de jeter la compréhension de la portée des niveaux de bloc, local, module et global à la fenêtre pour un mot clé. L'idée d'utiliser des blocs et des déclarations déterminant le moment où quelque chose tombe en dehors de la portée ou dont on dispose est énorme pour moi, et je pense que c'était la partie la plus effrayante de IOC Containers pour moi: remplacer ce développement très élémentaire par un mot clé d'extension .
Suamere
J'ai donc marqué votre réponse comme étant la réponse, car elle aborde vraiment le cœur du problème. Et à ce que j’ai lu, c’est que vous devez faire confiance à ce que les conteneurs d’IOC font sous la couverture, comme tout le monde. Et en ce qui concerne les possibilités d’utilisation abusive et l’élégance du traitement, c’est un sujet de préoccupation pour un magasin et les critiques de code, qui doivent donc faire preuve de la diligence requise pour suivre de bons principes de développement.
Suamere
2
En ce qui concerne 4, Dependency Injection n’est pas l’ acronyme SOLID . 'D' = 'Principe d'inversion de dépendance', qui est un concept totalement indépendant.
astreltsov
@astr Bon point. J'ai remplacé "injection" par "inversion" car je pense que c'est ce que je voulais écrire en premier lieu. Cela reste un peu une simplification excessive, car l'inversion de dépendance n'implique pas nécessairement plusieurs implémentations concrètes, mais je pense que le sens est suffisamment clair. Il serait dangereux de s'appuyer sur une abstraction polymorphe si le fait de renoncer aux implémentations était une violation OCP.
Ben Aaronson
27

Les conteneurs IOC ne violent-ils pas beaucoup de ces principes?

En théorie? Les conteneurs IoC ne sont qu'un outil. Vous pouvez avoir des objets qui ont une responsabilité unique, des interfaces bien séparées, leur comportement ne peut pas être modifié une fois écrits, etc.

En pratique? Absolument.

J'ai entendu dire que peu de programmeurs ont déclaré qu'ils utilisaient des conteneurs IoC spécifiquement pour vous permettre d'utiliser une configuration pour modifier le comportement du système, en violation du principe d'ouverture / fermeture. Il y avait des blagues sur le codage en XML car 90% de leur travail consistait à corriger la configuration de Spring. Et vous avez certainement raison de dire que masquer les dépendances (ce qui n’est pas le cas de tous les conteneurs IoC) est un moyen rapide et facile de créer un code hautement couplé et très fragile.

Regardons les choses différemment. Considérons une classe très basique ayant une seule dépendance basique. Cela dépend d'un Action, d'un délégué ou d'un foncteur qui ne prend aucun paramètre et retourne void. Quelle est la responsabilité de cette classe? Vous ne pouvez pas dire. Je pourrais nommer cette dépendance quelque chose de clair, CleanUpActionqui vous fait penser que cela fait ce que vous voulez. Dès que l'IoC entre en jeu, il devient n'importe quoi . N'importe qui peut entrer dans la configuration et modifier votre logiciel pour faire des choses assez arbitraires.

"En quoi est-ce différent de passer au Actionconstructeur?" tu demandes. C'est différent parce que vous ne pouvez pas changer ce qui est transmis au constructeur une fois le logiciel déployé (ou au moment de l'exécution!). C'est différent parce que vos tests unitaires (ou vos tests unitaires de consommateurs) ont couvert les scénarios dans lesquels le délégué est passé au constructeur.

"Mais c'est un exemple bidon! Mes affaires ne permettent pas de changer de comportement!" t'exclamons. Désolé de vous dire, mais c'est le cas. À moins que l'interface que vous injectez ne soit que des données. Et votre interface ne se limite pas à des données, car s'il ne s'agissait que de données, vous construiriez un objet simple et l'utiliseriez. Dès que vous avez une méthode virtuelle dans votre point d’injection, le contrat de votre classe utilisant IoC est "Je peux tout faire ".

"En quoi cela diffère-t-il de l'utilisation de scripts pour piloter des choses? C'est parfaitement correct." Certains d'entre vous demandent. Ce n'est pas différent Du point de vue de la conception du programme, il n'y a pas de différence. Vous appelez votre programme pour exécuter du code arbitraire. Et bien sûr, cela se fait dans les jeux depuis des lustres. Cela se fait dans des tonnes d'outils d'administration de systèmes. Est-ce la POO? Pas vraiment.

Il n'y a aucune excuse pour leur omniprésence actuelle. Les conteneurs IoC ont un seul objectif: coller vos dépendances lorsque vous avez autant de combinaisons ou si elles se déplacent si rapidement qu'il est impossible de rendre ces dépendances explicites. Donc, très peu d’entreprises correspondent réellement à ce scénario.

Telastyn
la source
3
Droite. Et beaucoup de magasins prétendent que leur produit est si complexe qu'ils entrent dans cette catégorie alors qu'ils ne le font pas vraiment. Comme c'est la vérité avec de nombreux scénarios.
Suamere
12
Mais IoC est exactement le contraire de ce que vous avez dit. Il utilise Open / Closeeness of du programme. Si vous pouvez changer le comportement du programme entier sans avoir à changer le code lui-même.
Euphoric le
6
@Euphoric - ouvert pour extension, fermé pour modification. Si vous pouvez changer le comportement du programme entier, il n'est pas fermé pour modification, n'est-ce pas? La configuration d'IoC reste votre logiciel, il s'agit toujours du code utilisé par vos classes pour fonctionner - même si c'est en XML plutôt qu'en Java ou en C # ou autre.
Telastyn
4
@Telastyn Le "fermé pour modification" signifie qu'il ne devrait pas être nécessaire de changer (modifier) ​​le code, pour changer le comportement d'un tout. Avec n'importe quelle configuration externe, vous pouvez simplement le diriger vers une nouvelle dll et avoir le comportement de tout un changement.
Euphoric
12
@Telastyn Ce n'est pas ce que dit le principe d'ouverture / fermeture. Sinon, une méthode prenant des paramètres violerait OCP car je pourrais "modifier" son comportement en lui transmettant différents paramètres. De même, dans votre version d'OCP, il serait en conflit direct avec DI, ce qui rendrait plutôt étrange que les deux apparaissent dans l'acronyme SOLID.
Ben Aaronson le
14

L'utilisation d'un conteneur IoC enfreint-elle nécessairement les principes de la POO / SOLID? Non .

Pouvez-vous utiliser les conteneurs IoC de manière à enfreindre les principes de la POO / SOLID? Oui .

Les conteneurs IoC ne sont que des cadres de gestion des dépendances dans votre application.

Vous avez mentionné les injections paresseuses, les fixateurs de propriétés et quelques autres fonctionnalités que je n’ai pas encore ressenties le besoin d’utiliser, qui, à mon avis, peuvent vous donner une conception moins qu'optimale. Cependant, les utilisations de ces fonctionnalités spécifiques sont dues aux limitations de la technologie dans laquelle vous implémentez des conteneurs IoC.

Par exemple, ASP.NET WebForms, vous devez utiliser l'injection de propriété, car il n'est pas possible d'instancier un Page. Cela est compréhensible car WebForms n’a jamais été conçu dans l’esprit des paradigmes de POO stricts.

ASP.NET MVC, d’autre part, il est tout à fait possible d’utiliser un conteneur IoC en utilisant l’injection de constructeur très favorable OOP / SOLID.

Dans ce scénario (utilisant l'injection de constructeur), je crois qu'il promeut les principes SOLID pour les raisons suivantes:

  • Principe de responsabilité unique - Le fait d'avoir de nombreuses classes plus petites permet à ces classes d'être très spécifiques et d'avoir une raison d'être. L'utilisation d'un conteneur IoC facilite grandement leur organisation.

  • Open / Closed - C'est là où l'utilisation d'un conteneur IoC est vraiment brillante. Vous pouvez étendre les fonctionnalités d'une classe en injectant différentes dépendances. Avoir des dépendances que vous injectez vous permet de modifier le comportement de cette classe. Un bon exemple consiste à modifier la classe que vous injectez en écrivant à un décorateur pour lui ajouter une mise en cache ou une journalisation. Vous pouvez complètement changer les implémentations de vos dépendances si elles sont écrites dans un niveau d'abstraction approprié.

  • Principe de substitution de Liskov - Pas vraiment pertinent

  • Principe de séparation des interfaces - Vous pouvez affecter plusieurs interfaces à une seule heure concrète si vous le souhaitez. Tout conteneur IoC valant un grain de sel le fera facilement.

  • Inversion de dépendance - Les conteneurs IoC vous permettent d'effectuer facilement l'inversion de dépendance.

Vous définissez un ensemble de dépendances et déclarez les relations et les portées, ainsi que Bing bang boom: vous avez par magie un addon qui modifie votre code ou votre IL à un moment donné et qui permet de faire fonctionner les choses. Ce n'est pas explicite ou transparent.

À la fois explicite et transparente, vous devez indiquer à votre conteneur IoC comment connecter des dépendances et gérer les durées de vie des objets. Cela peut être par convention, par fichiers de configuration ou par code écrit dans les applications principales de votre système.

Je conviens que lorsque j'écris ou lis du code à l'aide de conteneurs IOC, cela élimine un code légèrement inélégant.

C’est toute la raison pour OOP / SOLID, de rendre les systèmes gérables. L'utilisation d'un conteneur IoC est en ligne avec cet objectif.

L'auteur de cette classe a déclaré: "Si nous n'utilisions pas de conteneur IOC, le constructeur aurait une douzaine de paramètres !!! C'est horrible!"

Une classe avec 12 dépendances est probablement une violation SRP quel que soit le contexte dans lequel vous l'utilisez.

Matthieu
la source
2
Excellente réponse. En outre, je pense que tous les principaux conteneurs d'IOC prennent également en charge le protocole AOP, ce qui peut être très utile pour OCP. Pour les problèmes transversaux, AOP est généralement un itinéraire plus convivial pour OCP qu'un décorateur.
Ben Aaronson
2
@BenAaronson Etant donné qu'AOP injecte du code dans une classe, je n'appellerais pas cela plus convivial pour OCP. Vous ouvrez et modifiez avec force une classe lors de la compilation / exécution.
Doval
1
@Doval Je ne vois pas comment cela injecte du code dans une classe. Remarque: je parle du style Castle DynamicProxy, dans lequel vous avez un intercepteur, plutôt que du style de tissage IL. Mais un intercepteur est essentiellement un décorateur qui utilise une réflexion pour ne pas avoir à se coupler à une interface particulière.
Ben Aaronson le
"Inversion de dépendance - Les conteneurs IoC vous permettent d'effectuer facilement l'inversion de dépendance" - ils ne le font pas, ils exploitent simplement quelque chose qui est bénéfique, que vous ayez IoC ou non. Idem avec d'autres principes.
Den
Je ne comprends vraiment pas la logique de l'explication selon laquelle quelque chose n'est pas mauvais, du fait que c'est un cadre. Les ServiceLocators sont également définis en tant que frameworks et sont considérés comme mauvais :).
ipavlu
5

Les conteneurs IOC ne permettent-ils pas à la fois de casser et de laisser les codeurs casser beaucoup de principes de POO / SOLID?

Le staticmot-clé en C # permet aux développeurs de casser beaucoup de principes de la POO / SOLID, mais il reste un outil valable dans les bonnes circonstances.

Les conteneurs IoC permettent un code bien structuré et réduisent le fardeau cognitif des développeurs. Un conteneur IoC peut être utilisé pour faciliter le respect des principes SOLID. Bien sûr, il peut être utilisé à mauvais escient, mais si nous excluions l’utilisation de tout outil susceptible d’être utilisé à mauvais escient, nous devions abandonner complètement la programmation.

Ainsi, bien que ce discours soulève des arguments valables à propos d'un code mal écrit, ce n'est pas exactement une question et ce n'est pas exactement utile.

Stephen
la source
D'après ce que j'ai compris de IOC Containers, afin de gérer l'étendue, la paresse et l'accessibilité, le code compilé est modifié pour permettre de faire des choses qui cassent la POO / SOLID, mais cache cette mise en œuvre dans un cadre agréable. Mais oui, cela ouvre également d'autres portes pour une utilisation incorrecte. La meilleure réponse à ma question serait une clarification de la manière dont les conteneurs IOC sont mis en œuvre, qui ajoute ou non à ma propre recherche.
Suamere,
2
@Suamere - Je pense que vous utilisez peut-être une définition trop large de la COI. Celui que j'utilise ne modifie pas le code compilé. Il existe de nombreux autres outils non IOC qui modifient également le code compilé. Peut-être que votre titre devrait ressembler davantage à: "La modification du code compilé rompt-elle ...".
Cerad
@Suamere Je ne connais pas très bien l'IoC, mais je n'ai jamais entendu parler de la modification du code compilé par l'IoC. Souhaitez-vous s'il vous plaît fournir des informations sur les plates-formes / langues / cadres parlez-vous.
Euphoric le
1
"Un conteneur IoC peut être utilisé pour faciliter le respect des principes SOLID." - Pouvez-vous élaborer s'il vous plaît? Il serait utile d’expliquer comment chaque principe est utile. Et bien sûr, tirer parti de quelque chose ne revient pas à aider quelque chose à arriver (par exemple, DI).
Den
1
@Euphoric Je ne connais pas les frameworks .net dont parlait Suamere, mais Spring modifie certainement le code. L'exemple le plus évident est son support pour l'injection basée sur une méthode (par laquelle il redéfinit une méthode abstraite de votre classe pour renvoyer une dépendance qu'il gère). Il utilise également de manière intensive les proxys générés automatiquement afin d’envelopper votre code afin de fournir des comportements supplémentaires (par exemple, pour la gestion automatique des transactions). Une grande partie de ceci n’est pas réellement liée à son rôle en tant que cadre DID, cependant.
Jules
3

Je vois plusieurs erreurs et malentendus dans votre question. La première est qu’il manque la distinction entre l’injection de propriété / de champ, l’injection de constructeur et le localisateur de services. Il y a eu beaucoup de discussions (p. Ex. Guerres à la flamme) sur la meilleure façon de faire les choses. Dans votre question, vous semblez ne présumer que de l'injection de propriété et ignorer l'injection du constructeur.

Deuxièmement, vous supposez que les conteneurs IoC affectent en quelque sorte la conception de votre code. Ils ne le font pas, ou du moins, il ne devrait pas être nécessaire de changer la conception de votre code si vous utilisez IoC. Vos problèmes avec les classes avec de nombreux constructeurs sont le cas où IoC cache de vrais problèmes avec votre code (le plus souvent une classe qui a des ruptures de SRP) et les dépendances cachées sont l’une des raisons pour lesquelles les gens découragent l’utilisation de l’injection de propriété et du localisateur de services.

Je ne comprends pas où réside le problème du principe ouvert-fermé, je ne ferai donc aucun commentaire à ce sujet. Mais il vous manque une partie de SOLID, qui a une influence majeure sur ceci: principe d'inversion de dépendance. Si vous suivez DIP, vous découvrirez que l'inversion d'une dépendance introduit une abstraction dont l'implémentation doit être résolue lors de la création d'une instance de classe. Avec cette approche, vous obtiendrez un grand nombre de classes, qui nécessitent la réalisation de plusieurs interfaces et sont fournies à la construction pour fonctionner correctement. Si vous deviez alors fournir ces dépendances à la main, vous auriez à faire beaucoup de travail supplémentaire. Les conteneurs IoC facilitent ce travail et vous permettent même de le changer sans avoir à recompiler le programme.

Donc, la réponse à votre question est non. Les conteneurs IoC ne violent pas les principes de conception de la POO. Bien au contraire, ils résolvent les problèmes causés par le respect des principes SOLID (notamment le principe de dépendance-inversion).

Euphorique
la source
J'ai essayé d'appliquer IoC (Autofac) à un projet non trivial récemment. Réalisé, je devais faire un travail similaire avec des constructions non langagières (new () vs API): spécifier ce qui devrait être résolu en interfaces et ce qui devrait être résolu en classes concrètes, en précisant ce qui devrait être une instance et ce qui devrait être un singleton, s'assurer que l'injection de propriété fonctionne correctement pour atténuer une dépendance circulaire. Décidé d'abandonner. Fonctionne bien avec les contrôleurs dans ASP MVC cependant.
Den
@ Den Votre projet n'a évidemment pas été conçu avec l'inversion de dépendance. Et nulle part je n'ai dit que l'utilisation d'IoC est triviale.
Euphoric le
En fait, c’est la chose - j’utilise l’inversion de dépendance depuis le tout début. IoC affectant la conception au-delà est ma plus grande préoccupation. Par exemple, pourquoi utiliserais-je des interfaces partout pour simplifier IoC? Voir également le commentaire en haut de la question.
Den
@Den, techniquement, vous n'avez pas "besoin" d'utiliser les interfaces. Les interfaces aident à se moquer des tests unitaires. Vous pouvez également marquer les éléments dont vous avez besoin pour vous moquer de virtuel.
Fred