Dernier pilier des pratiques SOLID, l’inversion de dépendance est un gage de modularité, de simplification de maintenance et facilite tant la conception, que la réalisation. En revanche, il faut être prêt à saturer son code d’interface, et accepter de perdre de vue l’implémentation de ce que l’on ne développe pas. Explications de ce principe de confiance encadré par des contrats :
« Un composant de haut niveau ne doit pas dépendre de composants de bas niveau » et comme par nature les composants de bas niveau ne peuvent pas dépendre de composants de haut niveau, le couplage entre composants s’affaiblit.
Et comme un schéma est souvent plus explicite, voici une situation classique ne respectant pas l’inversion de dépendance :
Nous avons ici de la duplication de fonctionnalité, ainsi qu’une dépendance forte entre par exemple le métier et un mécanisme de log.
Pour obtenir l’inversion, nous allons passer par des contrats d’interface, à partir de ce moment, les composants ne dépendent plus de l’implémentation, mais uniquement de leur contrat d’interface.
Voici le résultat :
Le résultat est une disparition de la dépendance, permettant de remplacer et maintenir facilement chaque composant. Au passage, la duplication de fonctionnalités disparaît presque par magie.
A noter, un composant déjà écrit ne répond pas nécessairement au contrat d’interface que l’on souhaite (d’ailleurs en général, ce ne sera pas le cas). Dans ce cas, le design pattern « adapter » résout la situation avec simplicité. On insère donc une classe répondant à l’interface souhaitée et convertissant les appels dans l’interface à laquelle répond le composant « rebelle ».
Reste le problème de savoir comment fournir les interfaces aux composants les utilisant.
Plusieurs méthodes sont possibles, via les patterns « factory » ou « service locator » par exemple.
En dehors de la méthode concrète utilisée, le point vraiment important est le « point de composition unique ». C’est-à-dire que l’endroit où est indiqué quelle classe concrète réalise l’implémentation de chaque interface, soit unique dans l’application. Ce peut être un fichier de config ou une partie du code, mais toutes les interfaces utilisées et leurs implémentations doivent êtres décrites au même endroit.