J'essaie actuellement de comprendre SOLID. Donc, le principe d'inversion de dépendance signifie que deux classes quelconques doivent communiquer via des interfaces, pas directement. Exemple: Si class A
a une méthode qui attend un pointeur sur un objet de type class B
, cette méthode devrait en fait attendre un objet de type abstract base class of B
. Cela aide aussi pour Open / Close.
À condition d’avoir bien compris cela, ma question est de savoir si c’est une bonne pratique de l’appliquer à toutes les interactions de classe ou devrais-je essayer de penser en termes de couches ?
Je suis sceptique parce que nous payons un prix pour suivre ce principe. Dis, j'ai besoin d'implémenter une fonctionnalité Z
. Après analyse, je conclus que cette fonctionnalité Z
consiste en fonctionnalité A
, B
et C
. Je crée une classe de façadeZ
qui, à travers des interfaces, utilise des classes A
, B
et C
. Je commence à coder la mise en œuvre et à un moment je me rends compte que la tâche Z
consiste en fait de fonctionnalité A
, B
et D
. Maintenant, je dois supprimer l' C
interface, le C
prototype de classe et écrire une D
interface et une classe séparées . Sans interfaces, seule la classe aurait dû être remplacée.
En d'autres termes, pour changer quelque chose, je dois changer 1. l'appelant 2. l'interface 3. la déclaration 4. l'implémentation. Dans un python couplé directement la mise en œuvre, je besoin de changer seulement la mise en œuvre.
Réponses:
Dans de nombreux dessins animés ou d'autres médias, les forces du bien et du mal sont souvent illustrées par un ange et un démon assis sur les épaules du personnage. Dans notre récit ici, au lieu de bien et de mal, nous avons SOLID sur une épaule et YAGNI (vous n'en aurez pas besoin!) Assis sur l'autre.
Les principes SOLID utilisés au mieux conviennent parfaitement aux systèmes d'entreprise gigantesques, complexes et ultra-configurables. Pour les systèmes plus petits ou plus spécifiques, il n'est pas approprié de rendre tout ce qui est ridiculement flexible, car le temps que vous passez à résumer des tâches ne sera pas un avantage.
Passer des interfaces à la place de classes concrètes signifie parfois, par exemple, que vous pouvez facilement permuter la lecture d’un fichier contre un flux réseau. Cependant, pour une grande quantité de projets logiciels, ce genre de flexibilité est tout simplement pas toujours va être nécessaire, et vous pourriez tout aussi bien passer des classes de fichiers en béton et l' appeler un jour et épargner vos cellules du cerveau.
Une partie de l’art du développement logiciel consiste à avoir une bonne idée de ce qui est susceptible de changer avec le temps et de ce qui ne l’est pas. Pour les éléments susceptibles de changer, utilisez les interfaces et autres concepts SOLID. Pour les choses qui ne le seront pas, utilisez YAGNI et transmettez simplement des types concrets, oubliez les classes d'usine, oubliez toute connexion et configuration d'exécution, etc., et oubliez beaucoup des abstractions SOLID. D'après mon expérience, l'approche YAGNI s'est révélée correcte bien plus souvent qu'elle ne l'est pas.
la source
File
, alors elle prendra unIFile
travail effectué". Ensuite, ils ne peuvent pas facilement remplacer un flux réseau, car ils ont trop sollicité l'interface et que certainesIFile
méthodes de la méthode ne l'utilisent même pas, qui ne s'appliquent pas aux sockets, de sorte qu'un socket ne peut pas être implémentéIFile
. Une des choses pour lesquelles DI n’est pas une solution miracle, c’est inventer les bonnes abstractions (interfaces) :-)En termes simples:
L'application du DIP est à la fois facile et amusante . Ne pas bien concevoir le modèle du premier coup n'est pas une raison suffisante pour abandonner le DIP.
D'autre part, la programmation avec des interfaces et OOD peut ramener la joie au métier parfois obsolète de la programmation.
Certaines personnes disent que cela ajoute de la complexité, mais je pense que l'opossite est vrai. Même pour les petits projets. Cela facilite les tests et les moqueries. Cela fait que votre code a moins d'
case
instructions, si elles sont imbriquéesifs
. Il réduit la complexité cyclomatique et vous fait penser de manière nouvelle. Cela rend la programmation plus semblable à la conception et à la fabrication réelles.la source
Utilisez l' inversion de dépendance là où cela a du sens.
Un contre-exemple extrême est la classe "string" incluse dans de nombreuses langues. Il représente un concept primitif, essentiellement un tableau de caractères. En supposant que vous puissiez changer cette classe de base, cela n’a aucun sens d’utiliser D ici car vous n’aurez jamais besoin d’échanger l’état interne avec autre chose.
Si vous avez un groupe d'objets utilisés en interne dans un module qui ne sont exposés à aucun autre module ou réutilisés nulle part, l'utilisation de DI ne vaut probablement pas la peine.
À mon avis, il y a deux endroits où le DI devrait automatiquement être utilisé:
Dans les modules conçus pour l'extension. Si l'objectif d'un module est de l'étendre et de changer de comportement, il est parfaitement logique d'intégrer l'ID dès le début.
Dans les modules que vous refactoring dans le but de réutiliser le code. Vous avez peut-être codé une classe pour qu'elle fasse quelque chose, puis vous vous êtes rendu compte plus tard qu'avec un refactor, vous pouvez utiliser ce code ailleurs et il est nécessaire de le faire . C’est un excellent candidat pour DI et autres modifications d’extensibilité.
Les clés ici sont utilisées lorsque cela est nécessaire car cela introduira une complexité supplémentaire, et assurez-vous de mesurer ce besoin via des exigences techniques (point un) ou une révision de code quantitatif (point deux).
DI est un excellent outil, mais comme tout outil *, il peut être surutilisé ou mal utilisé.
* Exception à la règle ci-dessus: une scie alternative est l'outil idéal pour tout travail. Si cela ne résout pas votre problème, il sera supprimé. En permanence.
la source
String
, il existe de nombreux cas dans lesquels des représentations alternatives seraient utiles si le type comportait un bon ensemble d'opérations virtuelles (par exemple, copier une sous-chaîne dans une partie spécifiée d'unshort[]
rapport, indiquer si une la sous-chaîne contient ou peut contenir uniquement de l'ASCII, essayez de copier une sous-chaîne supposée ne contenir que de l'ASCII dans une partie spécifiée d'unbyte[]
, etc.)Il me semble que la question initiale manque une partie du point du DIP.
Pour tirer pleinement parti du DIP, vous devez d’abord créer la classe Z et lui demander d’appeler les fonctionnalités des classes A, B et C (qui ne sont pas encore développées). Cela vous donne l'API pour les classes A, B et C. Ensuite, créez des classes A, B et C et renseignez les détails. Vous devriez effectivement créer les abstractions dont vous avez besoin lorsque vous créez la classe Z, en vous basant entièrement sur ce dont elle a besoin. Vous pouvez même écrire des tests autour de la classe Z avant même que les classes A, B ou C ne soient écrites.
Rappelez-vous que le DIP dit que "les modules de haut niveau ne devraient pas dépendre de modules de bas niveau. Les deux devraient dépendre d'abstractions".
Une fois que vous avez déterminé ce dont la classe Z a besoin et la manière dont elle veut obtenir ce dont elle a besoin, vous pouvez ensuite entrer les détails. Bien sûr, il faudra parfois apporter des modifications à la classe Z, mais 99% du temps, ce ne sera pas le cas.
Il n’y aura jamais de classe D parce que vous avez déterminé que Z a besoin de A, B et C avant d’être écrit. Un changement dans les exigences est une histoire totalement différente.
la source
La réponse courte est "presque jamais", mais il y a en fait quelques endroits où le DIP n'a pas de sens:
Usines ou constructeurs, dont le travail consiste à créer des objets. Ce sont essentiellement les "nœuds feuilles" dans un système qui englobe totalement l'IoC. À un moment donné, quelque chose doit réellement créer vos objets et ne peut dépendre de rien d'autre pour le faire. Dans de nombreuses langues, un conteneur IoC peut le faire pour vous, mais vous devez parfois le faire à l'ancienne.
Implémentations de structures de données et d'algorithmes. En règle générale, dans ces cas, les caractéristiques essentielles pour lesquelles vous optimisez (telles que la durée d'exécution et la complexité asymptotique) dépendent des types de données spécifiques utilisés. Si vous implémentez une table de hachage, vous devez vraiment savoir que vous utilisez un tableau pour le stockage, pas une liste chaînée, et seule la table elle-même sait comment allouer correctement les tableaux. Vous ne voulez pas non plus passer dans un tableau mutable et demander à l'appelant de casser votre table de hachage en bidouillant son contenu.
Classes de modèle de domaine . Ceux-ci implémentent votre logique métier et (la plupart du temps), il est logique de n'avoir qu'une seule implémentation, car (la plupart du temps), vous ne développez le logiciel que pour une seule entreprise. Bien que certaines classes de modèle de domaine puissent être construites à l'aide d'autres classes de modèle de domaine, cela se fera généralement au cas par cas. Etant donné que les objets de modèle de domaine n'incluent aucune fonctionnalité pouvant être utilement simulée, il n'y a aucun avantage de testabilité ou de maintenabilité pour le DIP.
Tous les objets fournis en tant qu'API externe et devant créer d'autres objets dont vous ne souhaitez pas exposer publiquement les détails de la mise en œuvre. Cela relève de la catégorie générale "La conception de la bibliothèque est différente de la conception de l'application". Une bibliothèque ou un framework peut faire un usage libéral de DI en interne, mais devra éventuellement faire un travail réel, sinon ce n'est pas une bibliothèque très utile. Disons que vous développez une bibliothèque de réseau; vous ne voulez vraiment pas que le consommateur puisse fournir sa propre implémentation d'une prise. Vous pouvez utiliser en interne une abstraction d'un socket, mais l'API que vous exposez aux appelants va créer ses propres sockets.
Tests unitaires et tests doubles. Les faux et les talons sont supposés faire une chose et le faire simplement. Si vous avez un faux assez complexe pour vous inquiéter de l'injection de dépendance, il est probablement trop complexe (peut-être parce qu'il implémente une interface beaucoup trop complexe).
Il pourrait y en avoir plus Ce sont ceux que je vois assez fréquemment.
la source
Certains signes que vous appliquez DIP à un niveau trop bas, là où cela n’apporte aucune valeur:
Si c'est ce que vous voyez, il serait peut-être préférable que Z appelle C directement et ignore l'interface.
De même, je ne pense pas à la décoration de méthode par un cadre d'injection de dépendances / proxy dynamique (Spring, Java EE) de la même manière qu'un vrai SOLID DIP - cela ressemble plus à un détail d'implémentation du fonctionnement de la décoration de méthode dans cette pile technologique. La communauté Java EE considère qu’il s’agit d’une amélioration pour laquelle vous n’avez plus besoin des paires Foo / FooImpl comme vous le faisiez auparavant ( référence ). En revanche, Python prend en charge la décoration de fonction en tant que fonctionnalité de langage de premier ordre.
Voir aussi ce blog .
la source
Si vous inversez toujours vos dépendances, alors toutes vos dépendances sont à l'envers. Ce qui signifie que si vous avez commencé avec du code en désordre avec un nœud de dépendances, c'est ce que vous avez encore (vraiment), simplement inversé. C'est là que se pose le problème suivant: chaque modification d'une implémentation doit également modifier son interface.
Le point d’inversion des dépendances est l’inversion sélective des dépendances qui rendent les choses enchevêtrées. Ceux qui devraient aller de A à B en C le sont toujours, ce sont ceux qui allaient de C à A qui passent maintenant de A à C.
Le résultat devrait être un graphique de dépendance exempt de cycles - un DAG. Il existe différents outils permettant de vérifier cette propriété et de tracer le graphique.
Pour une explication plus complète, voir cet article :
la source