FP vs OOP

Il n'y a pas si longtemps, plusieurs publications sont apparues sur le hub qui contrastaient avec l'approche fonctionnelle et objet, ce qui a généré dans les commentaires une discussion animée sur ce qu'elle est vraiment - la programmation orientée objet et en quoi elle diffère de la fonctionnalité. Bien que je sois un peu en retard, je veux partager avec d'autres ce que pense Robert Martin, également connu sous le nom d'Oncle Bob.



Au cours des dernières années, j'ai pu à plusieurs reprises programmer en tandem avec des personnes qui étudiaient la programmation fonctionnelle et qui étaient partiales à l'égard de la POO. Cela était généralement exprimé sous la forme de déclarations comme: «Eh bien, c'est trop comme quelque chose d'objet.»


Je pense que cela vient de la conviction que FP et OOP s'excluent mutuellement. Beaucoup semblent penser que si le programme est fonctionnel, il n'est pas orienté objet. Je crois que la formation d'une telle opinion est une conséquence logique de l'étude de quelque chose de nouveau.


Lorsque nous adoptons une nouvelle technique, nous commençons souvent à éviter les anciennes techniques que nous utilisions auparavant. C'est naturel, car nous pensons que la nouvelle technique est "meilleure" et donc que l'ancienne technique est probablement "pire".


Dans ce post, je suis justifié à l'idée que, bien que la POO et la PF soient orthogonales, ce ne sont pas des concepts mutuellement exclusifs. Qu'un bon programme fonctionnel puisse (et devrait) être orienté objet. Et qu'un bon programme orienté objet peut (et devrait) être fonctionnel. Mais pour ce faire, nous devons déterminer les conditions.


Qu'est-ce que la POO?


J'aborderai la question dans une perspective réductionniste. Il existe de nombreuses définitions correctes de la POO qui couvrent de nombreux concepts, principes, techniques, modèles et philosophies. J'ai l'intention de les ignorer et de me concentrer sur le sel lui-même. Ici, le réductionnisme est nécessaire parce que toute cette richesse d'opportunités entourant la POO n'est pas vraiment quelque chose de spécifique à la POO; cela fait partie de la richesse des opportunités que l'on trouve dans le développement de logiciels en général. Ici, je me concentrerai sur la partie de la POO, qui est déterminante et inamovible.


Regardez deux expressions:


1: f (o); 2: de ();


Quelle est la différence?


Il n'y a clairement aucune différence sémantique. Toute la différence réside entièrement dans la syntaxe. Mais l'un semble procédural et l'autre est orienté objet. En effet, nous sommes habitués au fait que l'expression 2 implique implicitement une sémantique de comportement spécial que l'expression 1 n'a pas. Cette sémantique de comportement particulière est le polymorphisme.


Lorsque nous voyons l'expression 1. nous voyons la fonction f , qui est appelée dans laquelle l'objet o est transféré. Cela implique qu'il n'y a qu'une seule fonction nommée f, et non le fait qu'elle soit membre de la cohorte standard de fonctions entourant o.


D'un autre côté, lorsque nous voyons l'expression 2. nous voyons un objet avec le nom o auquel un message avec le nom f est envoyé. Nous nous attendons à ce qu'il puisse y avoir d'autres types d'objets qui reçoivent le message f et donc nous ne savons pas quel comportement spécifique attendre de f après l'appel. Le comportement dépend du type o. c'est-à-dire que f est polymorphe.


Ce fait que nous attendons des méthodes de comportement polymorphe est l'essence même de la programmation orientée objet. Il s'agit d'une définition réductionniste et cette propriété ne peut pas être supprimée de la POO. La POO sans polymorphisme n'est pas la POO. Toutes les autres propriétés de POO, telles que l'encapsulation des données et les méthodes liées à ces données et même l'héritage, sont plus liées à l'expression 1. qu'à l'expression 2.


Les programmeurs utilisant C et Pascal (et dans une certaine mesure même Fortran et Cobol) ont toujours créé des systèmes de fonctions et de structures encapsulées. Pour créer de telles structures, vous n'avez même pas besoin d'un langage de programmation orienté objet. L'encapsulation et même l'héritage simple dans de telles langues est évident et naturel. (En C et Pascal plus naturellement que dans les autres)


Par conséquent, ce qui distingue vraiment les programmes OOP des programmes non-OOP est le polymorphisme.


Vous voudrez peut-être faire valoir que le polyphorisme peut être fait simplement en utilisant l'interrupteur f intérieur ou de longues chaînes if / else. C'est vrai, j'ai donc besoin de définir une autre limitation pour la POO.


L'utilisation du polymorphisme ne doit pas créer la dépendance de l'appelant vis-à-vis de l'appelé.


Pour expliquer cela, regardons à nouveau les expressions. L'expression 1: f (o) semble dépendre de la fonction f au niveau du code source. Nous tirons cette conclusion parce que nous supposons également que f n'est qu'un et que l'appelant doit donc connaître l'appelé.


Cependant, lorsque nous regardons l'expression 2. de (), nous supposons autre chose. Nous savons qu'il peut y avoir de nombreuses réalisations de f et nous ne savons pas laquelle de ces fonctions f sera réellement appelée. Par conséquent, le code source contenant l'expression 2 est indépendant de la fonction appelée au niveau du code source.


Plus précisément, cela signifie que les modules (fichiers avec code source) qui contiennent des appels de fonctions polymorphes ne doivent pas faire référence à des modules (fichiers avec code source) qui contiennent l'implémentation de ces fonctions. Il ne peut y avoir d' inclusion, d' utilisation ou d' exigence ou d'autres mots clés qui rendent certains fichiers de code source dépendants d'autres.


Donc, notre définition réductionniste de la POO est:


Une technique utilisant le polymorphisme dynamique pour appeler des fonctions et ne pas créer de dépendances de l'appelant sur l'appelé au niveau du code source.

Qu'est-ce que la FA?


Et encore une fois, je vais utiliser l'approche réductionniste. FP a des traditions et une histoire riches, dont les racines sont plus profondes que la programmation elle-même. Il existe des principes, des techniques, des théorèmes, des philosophies et des concepts qui imprègnent ce paradigme. Je vais ignorer tout cela et aller directement à l'essentiel, à la propriété inhérente qui sépare FP des autres styles. Le voici:


f (a) == f (b) si a == b.


Dans un programme fonctionnel, l'appel d'une fonction avec le même argument donne le même résultat quelle que soit la durée d'exécution du programme. Ceci est parfois appelé transparence référentielle.


Il résulte de ce qui précède que f ne doit pas modifier les parties de l'état global qui affectent le comportement de f. De plus, si nous disons que f représente toutes les fonctions du système - c'est-à-dire que toutes les fonctions du système doivent être référentiellement transparentes - alors aucune fonction du système ne peut changer l'état global. Aucune fonction ne peut faire quoi que ce soit qui puisse conduire à une autre fonction du système renvoyant une valeur différente avec les mêmes arguments.


Cela a une conséquence plus profonde - aucune valeur nommée ne peut être modifiée. Autrement dit, il n'y a pas d'opérateur d'affectation.


Si vous examinez attentivement cette déclaration, vous pouvez conclure qu'un programme composé uniquement de fonctions transparentes et transparentes ne peut rien - car tout comportement utile du système modifie l'état de quelque chose; même si ce n'est que l'état de l'imprimante ou de l'écran. Cependant, si nous excluons le fer des exigences de transparence référentielle et de tous les éléments du monde qui nous entourent, il s'avère que nous pouvons créer des systèmes très utiles.


Bien entendu, l'accent est mis sur la récursivité. Considérons une fonction qui prend une structure avec état comme argument. Cet argument comprend toutes les informations d'état dont une fonction a besoin pour fonctionner. Une fois le travail terminé, la fonction crée une nouvelle structure avec un état dont le contenu est différent du précédent. Et avec la dernière action, la fonction s'appelle avec une nouvelle structure comme argument.


Ceci n'est qu'une des astuces simples qu'un programme fonctionnel peut utiliser pour stocker les changements d'état sans avoir à changer d'état [1].


Ainsi, la définition réductionniste de la programmation fonctionnelle:


Transparence référentielle - Vous ne pouvez pas réaffecter de valeurs.

FP vs OOP


À ce stade, les partisans de la POO et les partisans de la FI me regardent déjà à travers des viseurs optiques. Le réductionnisme n'est pas le meilleur moyen de se faire des amis. Mais parfois c'est utile. Dans ce cas, je pense qu'il est utile de faire la lumière sur l'holivar anti-OOP qui ne faiblit pas.


Il est clair que les deux définitions réductionnistes que j'ai choisies sont complètement orthogonales. Le polymorphisme et la transparence référentielle n'ont rien à voir l'un avec l'autre. Ils ne se coupent en aucune façon.


Mais l'orthogonalité n'implique pas l'exclusion mutuelle (demandez à James Clerk Maxwell). Il est tout à fait possible de créer un système qui utilise à la fois le polymorphisme dynamique et la transparence référentielle. Ce n'est pas seulement possible, c'est juste et bon!


Pourquoi cette combinaison est-elle bonne? Pour exactement les mêmes raisons que ses deux composants! Les systèmes construits sur le polymorphisme dynamique sont bons car ils ont une faible connectivité. Les dépendances peuvent être inversées et placées de différents côtés des limites architecturales. Ces systèmes peuvent être testés à l'aide de Moki et Fake et d'autres types de tests doubles. Les modules peuvent être modifiés sans apporter de modifications à d'autres modules. Par conséquent, ces systèmes sont plus faciles à modifier et à améliorer.


Les systèmes basés sur la transparence référentielle sont également bons car ils sont prévisibles. L'immuabilité des états rend ces systèmes plus faciles à comprendre, à modifier et à améliorer. Cela réduit considérablement la probabilité de races et d'autres problèmes de multithreading.


L'idée principale ici est la suivante:


Il n'y a pas d'holivar FP vs OOP

FP et OOP fonctionnent bien ensemble. Les deux sont bons et appropriés à utiliser dans les systèmes modernes. Le système, qui est basé sur une combinaison des principes de la POO et de la PF, maximise la flexibilité, la maintenabilité, la testabilité, la simplicité et la résistance. Si vous en supprimez un pour en ajouter un autre, cela ne fera qu'aggraver la structure du système.


[1] Puisque nous utilisons des machines avec l'architecture de Von Neumann, nous supposons qu'elles ont des cellules de mémoire dont l'état change réellement. Dans le mécanisme de récursion que j'ai décrit, l'optimisation de la récursion de queue de queue ne permettra pas la création de nouveaux cadres de verre et le cadre de verre d'origine sera utilisé. Mais cette violation de la transparence référentielle (généralement) est cachée au programmeur et n'affecte rien.

Source: https://habr.com/ru/post/fr474518/


All Articles