Je comprends les avantages de l'injection de dépendance elle-même. Prenons le printemps par exemple. Je comprends également les avantages d'autres fonctionnalités de Spring comme AOP, des aides de différents types, etc. Je me demande simplement quels sont les avantages de la configuration XML tels que:
<bean id="Mary" class="foo.bar.Female">
<property name="age" value="23"/>
</bean>
<bean id="John" class="foo.bar.Male">
<property name="girlfriend" ref="Mary"/>
</bean>
par rapport à l'ancien code Java code tel que:
Female mary = new Female();
mary.setAge(23);
Male john = new Male();
john.setGirlfriend(mary);
qui est plus facile à déboguer, à vérifier en temps de compilation et qui peut être compris par quiconque ne connaît que Java. Quel est donc l'objectif principal d'un cadre d'injection de dépendances? (ou un morceau de code qui montre ses avantages.)
MISE À JOUR:
En cas de
IService myService;// ...
public void doSomething() {
myService.fetchData();
}
Comment le framework IoC peut-il deviner quelle implémentation de myService je souhaite être injecté s'il y en a plusieurs? S'il n'y a qu'une seule implémentation d'une interface donnée et que je laisse le conteneur IoC décider automatiquement de l'utiliser, elle sera interrompue après qu'une deuxième implémentation apparaisse. Et s'il n'y a intentionnellement qu'une seule implémentation possible d'une interface, vous n'avez pas besoin de l'injecter.
Il serait vraiment intéressant de voir un petit morceau de configuration pour IoC qui montre ses avantages. J'utilise Spring depuis un moment et je ne peux pas fournir un tel exemple. Et je peux montrer des lignes simples qui démontrent les avantages de l'hibernation, du dwr et d'autres frameworks que j'utilise.
MISE À JOUR 2:
Je me rends compte que la configuration IoC peut être modifiée sans recompilation. Est-ce vraiment une si bonne idée? Je peux comprendre quand quelqu'un veut changer les informations d'identification de la base de données sans recompiler - il peut ne pas être développeur. Dans votre pratique, à quelle fréquence une autre personne que le développeur modifie-t-elle la configuration de l'IoC? Je pense que pour les développeurs, il n'y a aucun effort pour recompiler cette classe particulière au lieu de changer la configuration. Et pour les non-développeurs, vous voudrez probablement lui faciliter la vie et fournir un fichier de configuration plus simple.
MISE À JOUR 3:
Configuration externe de la cartographie entre les interfaces et leurs implémentations concrètes
Qu'est-ce qui est si bon pour le rendre extenal? Vous ne rendez pas tout votre code externe, alors que vous pouvez certainement - il suffit de le placer dans le fichier ClassName.Java.txt, de le lire et de le compiler manuellement à la volée - wow, vous avez évité la recompilation. Pourquoi la compilation devrait-elle être évitée?!
Vous gagnez du temps de codage car vous fournissez des mappages de manière déclarative, pas dans un code procédural
Je comprends que l'approche parfois déclarative fait gagner du temps. Par exemple, je déclare une seule fois qu'un mappage entre une propriété de bean et une colonne de base de données et hibernate utilise ce mappage lors du chargement, de l'enregistrement, de la construction de SQL basé sur HSQL, etc. C'est là que l'approche déclarative fonctionne. Dans le cas de Spring (dans mon exemple), la déclaration avait plus de lignes et avait la même expressivité que le code correspondant. S'il y a un exemple où une telle déclaration est plus courte que le code - j'aimerais le voir.
Le principe de l'inversion de contrôle permet des tests unitaires faciles car vous pouvez remplacer les implémentations réelles par de fausses (comme remplacer la base de données SQL par une base de données en mémoire)
Je comprends l'inversion des avantages du contrôle (je préfère appeler le modèle de conception discuté ici comme injection de dépendance, car l'IoC est plus général - il existe de nombreux types de contrôle, et nous n'en inversons qu'un seul - le contrôle de l'initialisation). Je demandais pourquoi quelqu'un avait besoin d'autre chose qu'un langage de programmation pour cela. Je peux définitivement remplacer de vraies implémentations par de fausses en utilisant du code. Et ce code exprimera la même chose que la configuration - il initialisera simplement les champs avec de fausses valeurs.
mary = new FakeFemale();
Je comprends les avantages de DI. Je ne comprends pas quels sont les avantages ajoutés par la configuration XML externe par rapport à la configuration du code qui fait de même. Je ne pense pas que la compilation doit être évitée - je compile tous les jours et je suis toujours en vie. Je pense que la configuration de DI est un mauvais exemple d'approche déclarative. La déclaration peut être utile si elle est déclarée une fois ET est utilisée plusieurs fois de différentes manières - comme hibernate cfg, où le mappage entre la propriété du bean et la colonne DB est utilisé pour l'enregistrement, le chargement, la construction de requêtes de recherche, etc. La configuration Spring DI peut être facilement traduite en la configuration du code, comme au début de cette question, n'est-ce pas? Et il n'est utilisé que pour l'initialisation du bean, n'est-ce pas? Ce qui signifie qu'une approche déclarative n'ajoute rien ici, n'est-ce pas?
Lorsque je déclare la mise en veille prolongée, je donne juste des informations à la mise en veille prolongée et cela fonctionne en fonction de cela - je ne lui dis pas quoi faire. En cas de printemps, ma déclaration indique au printemps exactement ce qu'il faut faire - alors pourquoi le déclarer, pourquoi ne pas simplement le faire?
DERNIÈRE MISE À JOUR:
Les gars, beaucoup de réponses me parlent de l'injection de dépendances, que JE SAIS IS BON. La question concerne le but de la configuration DI au lieu d'initialiser le code - j'ai tendance à penser que le code d'initialisation est plus court et plus clair. La seule réponse que j'ai obtenue jusqu'à présent à ma question, c'est qu'il évite la recompilation, lorsque la configuration change. Je suppose que je devrais poster une autre question, car c'est un grand secret pour moi, pourquoi la compilation devrait être évité dans ce cas.
Pour moi, l'une des principales raisons d'utiliser un IoC (et d'utiliser une configuration externe) réside dans les deux domaines suivants:
test
Si vous divisez vos tests en 3 scénarios (ce qui est assez normal dans le développement à grande échelle):
Ce que vous voudrez faire, c'est pour les deux derniers scénarios de test (intégration et boîte noire), ne recompiler aucune partie de l'application.
Si l'un de vos scénarios de test vous oblige à modifier la configuration (c'est-à-dire: utiliser un autre composant pour imiter une intégration bancaire ou effectuer une charge de performance), cela peut être facilement géré (cela présente les avantages de configurer le côté DI d'un IoC cependant.
De plus, si votre application est utilisée sur plusieurs sites (avec une configuration de serveur et de composant différente) ou si sa configuration change dans l'environnement en direct, vous pouvez utiliser les étapes ultérieures des tests pour vérifier que l'application gérera ces modifications.
Production
En tant que développeur, vous ne contrôlez pas (et ne devriez pas) contrôler l'environnement de production (en particulier lorsque votre application est distribuée à plusieurs clients ou sites séparés), pour moi, c'est le véritable avantage d'utiliser à la fois une IoC et une configuration externe. , car il appartient au support d'infrastructure/production de Tweak et d'ajuster l'environnement en direct sans avoir à revenir aux développeurs et à travers des tests (coût plus élevé quand tout ce qu'ils veulent faire est de déplacer un composant).
Résumé
Les principaux avantages que la configuration externe d'un IoC proviennent de donner aux autres (non-développeurs) le pouvoir de configurer votre application, d'après mon expérience, cela n'est utile que dans un ensemble limité de circonstances:
Dans la pratique, j'ai constaté que même lorsque vous développez quelque chose sur lequel vous contrôlez l'environnement, il sera exécuté, avec le temps, il est préférable de donner à quelqu'un d'autre les capacités de modifier la configuration:
Remarque: L'application fait référence à la solution complète (pas seulement l'exécutable), donc tous les fichiers nécessaires au fonctionnement de l'application .
L'injection de dépendance est un style de codage qui a ses racines dans l'observation que la délégation d'objet est généralement un modèle de conception plus utile que l'héritage d'objet (c'est-à-dire que l'objet a une relation est plus utile que l'objet est une relation). Un autre ingrédient est cependant nécessaire pour que DI fonctionne, celui de créer des interfaces d'objets. En combinant ces deux puissants modèles de conception, les ingénieurs logiciels ont rapidement réalisé qu'ils pouvaient créer du code flexible à couplage lâche et ainsi le concept de l'injection de dépendance est né. Cependant, ce n'est que lorsque la réflexion d'objet est devenue disponible dans certains langages de haut niveau que DI a vraiment pris son envol. Le composant de réflexion est au cœur de la plupart des systèmes DI d'aujourd'hui, car les aspects vraiment cool de la DI nécessitent la capacité de sélectionner des objets par programme et de les configurer et de les injecter dans d'autres objets à l'aide d'un système externe et indépendant des objets eux-mêmes.
Un langage doit fournir une bonne prise en charge à la fois des techniques de programmation orientées objet normales ainsi que la prise en charge des interfaces d'objet et de la réflexion d'objet (par exemple Java et C #). Bien que vous puissiez créer des programmes à l'aide de modèles DI en C++ systèmes son manque de prise en charge de la réflexion dans le langage proprement dit l'empêche de prendre en charge les serveurs d'applications et autres plates-formes DI et limite ainsi l'expressivité des modèles DI.
Points forts d'un système construit à l'aide de modèles DI:
Le code DI semble certainement plus lourd, les inconvénients d'avoir tous ces fichiers XML qui configurent les objets à injecter dans d'autres objets semblent difficiles. C'est pourtant le but des systèmes DI. Votre capacité à mélanger et à faire correspondre des objets de code sous la forme d'une série de paramètres de configuration vous permet de créer des systèmes complexes en utilisant du code tiers avec un codage minimal de votre part.
L'exemple fourni dans la question ne fait que toucher la surface du pouvoir expressif qu'une bibliothèque d'objets DI correctement factorisée peut fournir. Avec un peu de pratique et beaucoup d'autodiscipline, la plupart des praticiens en DI trouvent qu'ils peuvent construire des systèmes qui ont une couverture de test à 100% du code d'application. Ce seul point est extraordinaire. Il ne s'agit pas d'une couverture de test à 100% d'une petite application de quelques centaines de lignes de code, mais d'une couverture de test à 100% d'applications comprenant des centaines de milliers de lignes de code. Je suis incapable de décrire tout autre modèle de conception qui offre ce niveau de testabilité.
Vous avez raison en ce sens qu'une application de seulement 10s de lignes de code est plus facile à comprendre que plusieurs objets plus une série de fichiers de configuration XML. Cependant, comme pour les modèles de conception les plus puissants, les gains sont constatés à mesure que vous continuez à ajouter de nouvelles fonctionnalités au système.
En bref, les applications basées sur DI à grande échelle sont à la fois plus faciles à déboguer et plus faciles à comprendre. Bien que la configuration Xml ne soit pas "vérifiée au moment de la compilation", tous les services d'application dont cet auteur est au courant fourniront au développeur des messages d'erreur s'ils tentent d'injecter un objet ayant une interface incompatible dans un autre objet. Et la plupart offrent une fonction de "vérification" qui couvre toutes les configurations d'objets connues. Cela se fait facilement et rapidement en vérifiant que l'objet à injecter A implémente l'interface requise par l'objet B pour toutes les injections d'objets configurées.
C'est une question un peu chargée, mais j'ai tendance à convenir que d'énormes quantités de configuration xml ne représentent pas vraiment beaucoup d'avantages. J'aime que mes applications soient aussi légères que possible sur les dépendances, y compris les cadres lourds.
Ils simplifient le code la plupart du temps, mais ils ont également une surcharge de complexité qui rend la recherche de problèmes assez difficile (j'ai vu ces problèmes de première main, et directement Java je serais un beaucoup plus confortable à gérer).
Je suppose que cela dépend un peu du style et de ce que vous êtes à l'aise ... aimez-vous piloter votre propre solution et avoir l'avantage de la connaître à fond, ou miser sur des solutions existantes qui peuvent s'avérer difficiles lorsque la configuration n'est pas '' t juste à droite? C'est tout un compromis.
Cependant, la configuration XML est un peu ma bête noire ... J'essaye de l'éviter à tout prix.
Chaque fois que vous pouvez modifier votre code en données, vous faites un pas dans la bonne direction.
Coder n'importe quoi en tant que données signifie que votre code lui-même est plus général et réutilisable. Cela signifie également que vos données peuvent être spécifiées dans une langue qui leur correspond exactement.
En outre, un fichier XML peut être lu dans une interface graphique ou un autre outil et facilement manipulé de manière pragmatique. Comment feriez-vous cela avec l'exemple de code?
Je factorise constamment les choses que la plupart des gens implémenteraient sous forme de code dans les données, cela rend le code laissé beaucoup plus propre. Je trouve inconcevable que les gens créent un menu en code plutôt qu'en données - il devrait être évident que le faire en code est tout simplement faux à cause du passe-partout.
La raison d'utiliser un conteneur DI est que vous n'avez pas besoin d'avoir un milliard de propriétés préconfigurées dans votre code qui sont simplement des getters et des setters. Voulez-vous vraiment coder en dur tous ceux avec le nouveau X ()? Bien sûr, vous pouvez avoir une valeur par défaut, mais le conteneur DI permet la création de singletons qui est extrêmement facile et vous permet de vous concentrer sur les détails du code, pas sur la tâche diverse de l'initialiser.
Par exemple, Spring vous permet d'implémenter l'interface InitializingBean et d'ajouter une méthode afterPropertiesSet (vous pouvez également spécifier une "méthode init" pour éviter de coupler votre code à Spring). Ces méthodes vous permettront de vous assurer que toute interface spécifiée en tant que champ dans votre instance de classe est correctement configurée au démarrage, et vous n'aurez plus à vérifier la nullité de vos getters et setters (en supposant que vous autorisez vos singletons à rester thread-safe ).
De plus, il est beaucoup plus facile de faire des initialisations complexes avec un conteneur DI au lieu de les faire vous-même. Par exemple, j'aide à utiliser XFire (pas CeltiXFire, nous utilisons uniquement Java 1.4). L'application a utilisé Spring, mais elle a malheureusement utilisé le mécanisme de configuration services.xml de XFire. Quand une collection d'éléments était nécessaire pour déclarer qu'il avait ZÉRO ou plusieurs instances au lieu d'une ou plusieurs instances, j'ai dû remplacer une partie du code XFire fourni pour ce service particulier.
Certaines valeurs par défaut de XFire sont définies dans son schéma Spring beans. Donc, si nous utilisions Spring pour configurer les services, les beans auraient pu être utilisés. Au lieu de cela, ce qui s'est passé, c'est que j'ai dû fournir une instance d'une classe spécifique dans le fichier services.xml au lieu d'utiliser les beans. Pour ce faire, je devais fournir le constructeur et mettre en place les références déclarées dans la configuration XFire. Le vrai changement que je devais apporter exigeait que je surcharge une seule classe.
Mais, grâce au fichier services.xml, j'ai dû créer quatre nouvelles classes, en définissant leurs valeurs par défaut en fonction de leurs valeurs par défaut dans les fichiers de configuration Spring de leurs constructeurs. Si nous avions pu utiliser la configuration Spring, j'aurais pu dire:
<bean id="base" parent="RootXFireBean">
<property name="secondProperty" ref="secondBean" />
</bean>
<bean id="secondBean" parent="secondaryXFireBean">
<property name="firstProperty" ref="thirdBean" />
</bean>
<bean id="thirdBean" parent="thirdXFireBean">
<property name="secondProperty" ref="myNewBean" />
</bean>
<bean id="myNewBean" class="WowItsActuallyTheCodeThatChanged" />
Au lieu de cela, cela ressemblait plus à ceci:
public class TheFirstPointlessClass extends SomeXFireClass {
public TheFirstPointlessClass() {
setFirstProperty(new TheSecondPointlessClass());
setSecondProperty(new TheThingThatWasHereBefore());
}
}
public class TheSecondPointlessClass extends YetAnotherXFireClass {
public TheSecondPointlessClass() {
setFirstProperty(TheThirdPointlessClass());
}
}
public class TheThirdPointlessClass extends GeeAnotherXFireClass {
public TheThirdPointlessClass() {
setFirstProperty(new AnotherThingThatWasHereBefore());
setSecondProperty(new WowItsActuallyTheCodeThatChanged());
}
}
public class WowItsActuallyTheCodeThatChanged extends TheXFireClassIActuallyCareAbout {
public WowItsActuallyTheCodeThatChanged() {
}
public overrideTheMethod(Object[] arguments) {
//Do overridden stuff
}
}
Ainsi, le résultat net est que quatre classes supplémentaires Java Java ont dû être ajoutées à la base de code pour obtenir l'effet qu'une classe supplémentaire et quelques informations de conteneur de dépendance simples ont obtenues. Ce n'est pas le "exception qui confirme la règle", cette IS la règle ... la gestion des bizarreries dans le code est beaucoup plus propre lorsque les propriétés sont déjà fournies dans un conteneur DI et que vous les modifiez simplement pour qu'elles correspondent une situation particulière, qui se produit le plus souvent.
J'ai ta réponse
Il y a évidemment des compromis dans chaque approche, mais les fichiers de configuration XML externalisés sont utiles pour le développement d'entreprise dans lequel des systèmes de construction sont utilisés pour compiler le code et non votre IDE. En utilisant le système de build, vous voudrez peut-être injecter certaines valeurs dans votre code - par exemple la version de la build (qui pourrait être pénible d'avoir à mettre à jour manuellement chaque fois que vous compilez). La douleur est plus grande lorsque votre système de génération extrait du code d'un système de contrôle de version. La modification de valeurs simples au moment de la compilation vous obligerait à modifier un fichier, à le valider, à compiler, puis à revenir à chaque fois pour chaque modification. Ce ne sont pas des modifications que vous souhaitez valider dans votre contrôle de version.
Autres cas d'utilisation utiles concernant le système de construction et les configurations externes:
Mise à jour: Tous les exemples ci-dessus concernaient des choses qui ne nécessitaient pas nécessairement des dépendances sur les classes. Mais vous pouvez facilement créer des cas où à la fois un objet complexe et une automatisation sont nécessaires - par exemple:
Vous n'avez pas besoin de recompiler votre code chaque fois que vous modifiez quelque chose dans la configuration. Il simplifiera le déploiement et la maintenance du programme. Par exemple, vous pouvez échanger un composant avec un autre avec seulement 1 changement dans le fichier de configuration.
Vous pouvez insérer une nouvelle implémentation pour petite amie. Ainsi, une nouvelle femelle peut être injectée sans recompiler votre code.
<bean id="jane" class="foo.bar.HotFemale">
<property name="age" value="19"/>
</bean>
<bean id="mary" class="foo.bar.Female">
<property name="age" value="23"/>
</bean>
<bean id="john" class="foo.bar.Male">
<property name="girlfriend" ref="jane"/>
</bean>
(Ce qui précède suppose que Female et HotFemale implémentent la même interface GirlfFriend)
Dans le monde .NET, la plupart des frameworks IoC fournissent à la fois la configuration XML et le code.
StructureMap et Ninject, par exemple, utilisent des interfaces fluides pour configurer les conteneurs. Vous n'êtes plus contraint d'utiliser des fichiers de configuration XML. Spring, qui existe également dans .NET, s'appuie fortement sur les fichiers XML car il s'agit de son interface de configuration principale historique, mais il est toujours possible de configurer des conteneurs par programmation.
D'un perspecitve de printemps, je peux vous donner deux réponses.
Tout d'abord, la configuration XML n'est pas le seul moyen de définir la configuration. La plupart des choses peuvent être configurées à l'aide d'annotations et les choses qui doivent être faites avec XML sont la configuration du code que vous n'écrivez pas de toute façon, comme un pool de connexions que vous utilisez à partir d'une bibliothèque. Spring 3 comprend une méthode pour définir la configuration DI en utilisant Java similaire à la configuration DI roulée à la main dans votre exemple. Donc, utiliser Spring ne signifie pas que vous devez utiliser un fichier de configuration basé sur XML.
Deuxièmement, Spring est bien plus qu'un simple framework DI. Il possède de nombreuses autres fonctionnalités, notamment la gestion des transactions et l'AOP. La configuration Spring XML mélange tous ces concepts. Souvent, dans le même fichier de configuration, je spécifie les dépendances de bean, les paramètres de transaction et j'ajoute des beans de portée de session qui sont réellement gérés à l'aide d'AOP en arrière-plan. Je trouve que la configuration XML fournit un meilleur endroit pour gérer toutes ces fonctionnalités. Je pense également que la configuration basée sur les annotations et la configuration XML évoluent mieux que de faire Java.
Mais je vois votre point et il n'y a rien de mal à définir la configuration d'injection de dépendance en Java. Normalement, je le fais moi-même dans les tests unitaires et lorsque je travaille sur un projet suffisamment petit pour ne pas avoir ajouté de framework DI. Je ne spécifie pas normalement la configuration dans Java parce que pour moi c'est le genre de code de plomberie que j'essaie d'échapper à l'écriture quand j'ai choisi d'utiliser Spring. C'est une préférence cependant, il ne le fait pas ne signifie pas que la configuration XML est supérieure à Java.
Facilité combinant des configurations partielles dans une configuration finale complète.
Par exemple, dans les applications Web, le modèle, la vue et les contrôleurs sont généralement spécifiés dans des fichiers de configuration distincts. Utilisez l'approche déclarative, vous pouvez charger, par exemple:
UI-context.xml
Model-context.xml
Controller-context.xml
Ou chargez avec une interface utilisateur différente et quelques contrôleurs supplémentaires:
AlternateUI-context.xml
Model-context.xml
Controller-context.xml
ControllerAdditions-context.xml
Faire de même dans le code nécessite une infrastructure pour combiner des configurations partielles. Pas impossible à faire dans le code, mais certainement plus facile à faire en utilisant un framework IoC.
Souvent, le point important est qui change la configuration après l'écriture du programme. Avec la configuration dans le code, vous supposez implicitement que la personne qui le modifie a les mêmes compétences et accès au code source, etc. que l'auteur d'origine.
Dans les systèmes de production, il est très pratique d'extraire un sous-ensemble de paramètres (par exemple, l'âge dans votre exemple) dans un fichier XML et d'autoriser par exemple administrateur système ou support personnel pour modifier la valeur sans leur donner le plein pouvoir sur le code source ou d'autres paramètres - ou simplement pour les isoler des complexités.
L'initialisation dans un fichier de configuration XML simplifiera votre travail de débogage/adaptation avec un client qui a déployé votre application sur leurs ordinateurs. (Parce qu'il ne nécessite pas de recompilation + remplacement de fichiers binaires)
Spring a également un chargeur de propriétés. Nous utilisons cette méthode pour définir des variables qui dépendent de l'environnement (par exemple développement, test, acceptation, production, ...). Cela pourrait être par exemple la file d'attente à écouter.
S'il n'y a aucune raison pour que la propriété change, il n'y a également aucune raison de la configurer de cette manière.
Votre cas est très simple et n'a donc pas besoin d'un conteneur IoC (Inversion of Control) comme Spring. D'un autre côté, lorsque vous "programmez sur des interfaces, pas sur des implémentations" (ce qui est une bonne pratique en POO), vous pouvez avoir du code comme ceci:
IService myService;
// ...
public void doSomething() {
myService.fetchData();
}
(notez que le type de myService est IService - une interface, pas une implémentation concrète). Maintenant, il peut être pratique de laisser votre conteneur IoC fournir automatiquement l'instance concrète correcte de IService lors de l'initialisation - lorsque vous avez de nombreuses interfaces et de nombreuses implémentations, il peut être fastidieux de le faire à la main. Les principaux avantages d'un conteneur IoC (framework d'injection de dépendances) sont: