J'ai récemment lu un livre intitulé Functional Programming in C # et il me semble que la nature immuable et sans état de la programmation fonctionnelle produit des résultats similaires aux modèles d'injection de dépendance et est peut-être même une meilleure approche, en particulier en ce qui concerne les tests unitaires.
Je serais reconnaissant si quelqu'un qui a de l'expérience avec les deux approches pouvait partager ses pensées et ses expériences afin de répondre à la question principale: la programmation fonctionnelle est-elle une alternative viable aux modèles d'injection de dépendance?
Réponses:
La gestion des dépendances est un gros problème dans la POO pour les deux raisons suivantes:
La plupart des programmeurs OO considèrent que le couplage étroit des données et du code est tout à fait bénéfique, mais cela a un coût. La gestion du flux de données à travers les couches est une partie inévitable de la programmation dans n'importe quel paradigme. Le couplage de vos données et de votre code ajoute le problème supplémentaire que si vous souhaitez utiliser une fonction à un certain point, vous devez trouver un moyen pour que son objet atteigne ce point.
L'utilisation d'effets secondaires crée des difficultés similaires. Si vous utilisez un effet secondaire pour certaines fonctionnalités, mais que vous souhaitez pouvoir échanger son implémentation, vous n'avez pratiquement pas d'autre choix que d'injecter cette dépendance.
Prenons comme exemple un programme de spammeur qui gratte les pages Web pour les adresses e-mail puis les envoie par e-mail. Si vous avez un état d'esprit DI, en ce moment, vous pensez aux services que vous encapsulerez derrière les interfaces, et quels services seront injectés où. Je vais laisser cette conception comme un exercice pour le lecteur. Si vous avez un état d'esprit FP, en ce moment, vous pensez aux entrées et sorties pour la couche de fonctions la plus basse, comme:
Lorsque vous pensez en termes d'entrées et de sorties, il n'y a pas de dépendances de fonction, uniquement des dépendances de données. C'est ce qui les rend si faciles à tester unitairement. Le calque suivant vous permet d'alimenter la sortie d'une fonction dans l'entrée de la suivante, et peut facilement échanger les différentes implémentations selon vos besoins.
Dans un sens très réel, la programmation fonctionnelle vous pousse naturellement à toujours inverser vos dépendances de fonction, et donc vous n'avez généralement pas à prendre de mesures spéciales pour le faire après coup. Lorsque vous le faites, des outils tels que des fonctions d'ordre supérieur, des fermetures et des applications partielles facilitent l'exécution avec moins de passe-partout.
Notez que ce ne sont pas les dépendances elles-mêmes qui posent problème. Ce sont les dépendances qui pointent dans le mauvais sens. La couche suivante peut avoir une fonction comme:
Il est parfaitement normal que cette couche ait des dépendances codées en dur comme celle-ci, car son seul but est de coller les fonctions de la couche inférieure ensemble. L'échange d'une implémentation est aussi simple que de créer une composition différente:
Cette recomposition facile est rendue possible par un manque d'effets secondaires. Les fonctions de la couche inférieure sont complètement indépendantes les unes des autres. La couche suivante peut choisir celle qui
processText
est réellement utilisée en fonction d'une configuration utilisateur:Encore une fois, ce n'est pas un problème car toutes les dépendances pointent dans un sens. Nous n'avons pas besoin d'inverser certaines dépendances pour les faire pointer toutes de la même manière, car les fonctions pures nous y obligent déjà.
Notez que vous pouvez rendre cela beaucoup plus couplé en passant
config
par la couche la plus basse au lieu de la vérifier en haut. FP ne vous empêche pas de faire cela, mais cela a tendance à le rendre beaucoup plus ennuyeux si vous essayez.la source
System.String
. Un système de modules vous permettrait de remplacerSystem.String
par une variable afin que le choix de l'implémentation de chaîne ne soit pas codé en dur, mais toujours résolu au moment de la compilation.Cela me semble être une question étrange. Les approches de programmation fonctionnelle sont largement tangentielles à l'injection de dépendances.
Bien sûr, avoir un état immuable peut vous pousser à ne pas "tricher" en ayant des effets secondaires ou en utilisant l'état de classe comme contrat implicite entre les fonctions. Cela rend le transfert de données plus explicite, ce qui, je suppose, est la forme la plus élémentaire d'injection de dépendance. Et le concept de programmation fonctionnelle de passer des fonctions rend cela beaucoup plus facile.
Mais cela ne supprime pas les dépendances. Vos opérations ont toujours besoin de toutes les données / opérations dont elles avaient besoin lorsque votre état était modifiable. Et vous devez toujours y mettre ces dépendances. Je ne dirais donc pas que les approches de programmation fonctionnelles remplacent DI, donc il n'y a aucune sorte d'alternative.
Si quoi que ce soit, ils viennent de vous montrer à quel point le code OO peut créer des dépendances implicites auxquelles les programmeurs pensent rarement.
la source
La réponse rapide à votre question est: Non .
Mais comme d’autres l’ont affirmé, la question associe deux concepts quelque peu indépendants.
Faisons cette étape par étape.
DI donne un style non fonctionnel
Au cœur de la programmation des fonctions se trouvent des fonctions pures - des fonctions qui mappent l'entrée à la sortie, de sorte que vous obtenez toujours la même sortie pour une entrée donnée.
DI signifie généralement que votre unité n'est plus pure puisque la sortie peut varier en fonction de l'injection. Par exemple, dans la fonction suivante:
getBookedSeatCount
(une fonction) peut varier et produire des résultats différents pour la même entrée donnée. Cela rendbookSeats
aussi impur.Il existe des exceptions à cela - vous pouvez injecter l'un des deux algorithmes de tri qui implémentent le même mappage d'entrée-sortie, bien qu'en utilisant des algorithmes différents. Mais ce sont des exceptions.
Un système ne peut pas être pur
Le fait qu'un système ne peut pas être pur est également ignoré comme cela est affirmé dans les sources de programmation fonctionnelles.
Un système doit avoir des effets secondaires, les exemples évidents étant:
Une partie de votre système doit donc impliquer des effets secondaires et cette partie peut également impliquer un style impératif ou un style OO.
Le paradigme shell-core
En empruntant les termes de l'excellent discours de Gary Bernhardt sur les frontières , une bonne architecture de système (ou module) comprendra ces deux couches:
Le point clé à retenir est de «diviser» le système en sa partie pure (le noyau) et la partie impure (la coque).
Bien qu'il offre une solution (et une conclusion) légèrement erronée, cet article de Mark Seemann propose le même concept. L'implémentation Haskell est particulièrement intéressante car elle montre que tout peut être fait en utilisant FP.
DI et FP
L'emploi de DI est parfaitement raisonnable même si la majeure partie de votre application est pure. La clé est de confiner le DI dans la coquille impure.
Un exemple sera les stubs API - vous voulez la vraie API en production, mais utilisez des stubs dans les tests. Adhérer au modèle shell-core aidera beaucoup ici.
Conclusion
FP et DI ne sont donc pas exactement des alternatives. Vous êtes susceptible d'avoir les deux dans votre système, et le conseil est d'assurer la séparation entre la partie pure et la partie impure du système, où FP et DI résident respectivement.
la source
Du point de vue POO, les fonctions peuvent être considérées comme des interfaces à méthode unique.
L'interface est un contrat plus fort qu'une fonction.
Si vous utilisez une approche fonctionnelle et faites beaucoup de DI, alors en comparaison avec une approche POO, vous obtiendrez plus de candidats pour chaque dépendance.
contre
la source