Quelqu'un sur le podcast Herding Code n° 68, http://herdingcode.com/herding-code-68-new-year-shenanigans/, a déclaré que les conteneurs IOC n'avaient pas leur place avec Python ou Javascript, ou des mots à cet effet. Je suppose que c'est une sagesse conventionnelle et que cela s'applique à tous les langages dynamiques. Pourquoi ? Qu'est-ce qui rend les conteneurs IOC inutiles dans les langages dynamiques ?
Réponses
Trop de publicités?IoC fournit un mécanisme pour rompre le couplage que l'on obtient lorsqu'un objet appelle 'new' sur une autre classe. Ce couplage lie l'objet appelant avec l'implémentation instanciée de l'interface qu'il implémente.
Dans les langages statiques lorsque vous référencez une classe par son nom (pour appeler new
dessus), il n'y a pas d'ambiguïté. Il s'agit d'un couplage fort à une classe spécifique.
Dans les langages dynamiques, appeler new X
est un espace réservé pour "instancier n'importe quelle classe définie comme X
au point d'exécution". Il s'agit d'un couplage plus lâche, car il est seulement lié au nom X
.
Cette différence subtile signifie que dans un langage dynamique, vous pouvez généralement changer ce qu'est X
, de sorte que la décision quant à quelle classe est instanciée reste modifiable en dehors de la classe appelante.
Cependant, personnellement, je trouve qu'il y a deux avantages à IoC que je n'obtiens pas en me fiant au langage dynamique pour permettre l'injection.
Un des effets secondaires du passage des dépendances par les constructeurs est que vous vous retrouvez avec des classes "blocs de construction" très découplées, réutilisables et faciles à tester. Elles n'ont aucune idée du contexte dans lequel elles sont censées être utilisées, donc vous pouvez les réutiliser partout.
L'autre résultat est d'avoir un code explicite pour faire le câblage. Fait correctement, cela représente clairement la structure de votre application et sa décomposition en sous-systèmes et cycles de vie. Cela oblige les individus à décider explicitement du cycle de vie ou du sous-système auquel ils veulent associer leur classe (lors de l'écriture du code de câblage), et à se concentrer sur le comportement de l'objet lors de l'écriture de la classe.
Comme l'a dit Jörg W Mittag.. "Ces outils sont inutiles, les principes de conception ne le sont pas." Je crois qu'ils sont inutiles, mais bien faits, ils restent précieux.
Je pense différemment. Je pense que les conteneurs IOC ont certainement un rôle dans les langages dynamiques.
Je ne partage pas l'opinion selon laquelle le fait qu'un langage soit dynamique élimine le besoin d'une composition claire des objets. Ou que un langage dynamique 'fournit' les mêmes fonctionnalités.
Un conteneur IOC est simplement un outil pour gérer cette organisation.
Même dans un langage dynamique, je veux 'brancher' ensemble des composants. Sans créer de dépendances fortes entre ces composants. Ou peut-être même sans spécifier la classe d'implémentation réelle de ces composants.
Je suis d'accord avec les réponses ci-dessus, mais je voulais aussi ajouter quelque chose ici concernant les tests :
Dans les systèmes complexes où il y a des interactions entre les sous-systèmes, l'injection de dépendance est la meilleure façon dont je suis au courant de faire des tests unitaires.
Si vous avez une unité logique X, qui a des interactions connues avec l'unité logique Y, vous pouvez créer un MockY qui a un comportement prédéfini et tester explicitement la logique de X.
Sans injection de dépendance, écrire des tests est un cauchemar. Vous ne pouvez pas obtenir une bonne couverture de code. Certains frameworks (par ex. django) contournent ce problème en lançant des instances de bases de données fictives pour les tests, etc. mais c'est essentiellement une mauvaise solution au problème.
Il devrait y avoir deux types de tests :
- Tests unitaires qui s'exécutent dans N'IMPORTE QUEL environnement et testent la logique des unités de code individuelles.
- Tests d'intégration/fonctionnels qui testent la logique d'application combinée.
Passons à la question : IoC. À quoi sert IoC ? C'est pratique pour quelques choses, mais c'est vraiment très bon pour rendre l'injection de dépendance plus facile :
// Faites ceci à chaque fois que vous voulez une instance de monTypeService
var SystemA = new SystemA()
var SystemB = new SystemB()
var SystemC = new SystemC(SystemA, "AutreChose")
var SystemD = new SystemD(SystemB, SystemC)
var IRepo = new MySqlRepo()
var monService = new monTypeService(SystemD, IRepo)
Transformez cette logique en :
// Faites ceci au démarrage de l'application
Container.Register(ISystemA, SystemA)
Container.Register(ISystemB, SystemB)
Container.Register(ISystemC, SystemC)
Container.Register(ISystemD, SystemD)
Container.Register(IRepo, MySqlRepo)
Container.Register(monTypeService)
// Faites ceci à tout moment
var monService = Container.resolve(monTypeService)
Maintenant, pourquoi ne voyons-nous pas l'IoC dans de nombreux langages dynamiques ?
Je dirais que la raison est que nous ne voyons pas beaucoup d'injection de dépendance dans ces langages.
...et cela serait parce que généralement les tests effectués dans ces langages sont inexistants.
J'ai entendu toutes sortes d'excuses pour cela ; interagir avec le DOM rend les tests difficiles, mon code est assez simple pour ne pas nécessiter de tests, les langages dynamiques n'ont pas besoin de tests unitaires car ils sont incroyables et expressifs.
C'est du n'importe quoi.
Il n'y a aucune excuse pour un projet sans tests unitaires ou des tests unitaires avec une mauvaise couverture de code.
...mais c'est étonnant le nombre de projets javascript et python que j'ai vus (en ciblant spécifiquement ces deux langages uniquement car ce sont des domaines d'intérêt et j'ai vu plus de projets de ce type que d'autres) sans IoC, sans DI et, sans surprise, sans tests.
Il y a un excellent article sur l'injection de dépendance sur le site de guice ici : http://code.google.com/p/google-guice/wiki/Motivation
Il n'y a rien dans les langages dynamiques qui résolve aucun de ces problèmes.
Résumé :
- IoC est utile pour certaines choses, mais principalement pour implémenter l'ID
- IoC n'est PAS des fichiers de configuration xml. >_<
- DI est utile pour les tests
- L'absence d'IoC indique l'absence de DI, qui indique l'absence de bons tests.
- Utilisez IoC.
Parce qu'ils sont déjà intégrés dans le langage.
Un conteneur IoC fournit deux choses :
- liaison dynamique
- un langage dynamique (généralement un très mauvais, basé sur XML ou dans les versions plus récentes sur des annotations Java/attributs .NET)
La liaison dynamique fait déjà partie du langage dynamique et le langage dynamique est déjà un langage dynamique. Par conséquent, un conteneur IoC n'a tout simplement pas de sens : le langage est déjà un conteneur IoC.
Une autre façon de le voir : qu'est-ce qu'un conteneur IoC vous permet de faire ? Il vous permet de prendre des composants indépendants et de les assembler dans une application, sans que les composants ne se connaissent mutuellement. Il y a un nom pour assembler des éléments indépendants dans une application : script ! (C'est à peu près la définition du script.) De nombreux langages dynamiques se trouvent également être très bons en scripting, donc ils sont parfaits en tant que conteneurs IoC.
Veuillez noter que je ne parle pas d'injection de dépendance ou d'inversion de contrôle. L'ID et l'IoC sont aussi importants dans les langages dynamiques que dans les langages statiques, pour exactement les mêmes raisons. Ce dont je parle ce sont des conteneurs IoC et des cadres d'ID. Ces outils sont inutiles, les principes de conception ne le sont pas.
L'une des caractéristiques principales des conteneurs IOC est que vous pouvez automatiquement "relier" vos modules ensemble au moment de l'exécution. Dans les langages dynamiques, vous pouvez le faire assez facilement sans aucune logique basée sur la réflexion sophistiquée. Cependant, les conteneurs IOC sont un motif utile que beaucoup de gens comprennent et il peut parfois être bénéfique d'utiliser le même style de conception. Voir cet article pour un autre point de vue.