Pourquoi un modèle de domaine anémique est-il considéré comme mauvais en C # / OOP, mais très important en F # / FP?

46

Dans un article de blog sur F # pour le plaisir et le profit, il est écrit:

Dans une conception fonctionnelle, il est très important de séparer le comportement des données. Les types de données sont simples et "stupides". Et puis séparément, vous avez un certain nombre de fonctions qui agissent sur ces types de données.

C'est l'opposé exact d'une conception orientée objet, où comportement et données doivent être combinés. Après tout, c'est exactement ce que sont les cours. En fait, dans une conception véritablement orientée objet, vous ne devez avoir que du comportement: les données sont privées et ne peuvent être consultées que via des méthodes.

En fait, dans OOD, ne pas avoir assez de comportement autour d'un type de données est considéré comme une mauvaise chose et a même un nom: le " modèle de domaine anémique ".

Étant donné qu'en C #, nous semblons continuer à emprunter à F # et à essayer d'écrire un code de style plus fonctionnel; comment se fait-il que nous n'empruntions pas l'idée de séparer les données / le comportement, et même de le considérer comme mauvais? Est-ce simplement que la définition ne correspond pas à la POO, ou y a-t-il une raison concrète pour laquelle c'est mauvais en C # qui, pour une raison quelconque, ne s'applique pas en F # (et en fait, est inversée)?

(Remarque: je suis particulièrement intéressé par les différences de C # / F # qui pourraient changer l'opinion de ce qui est bon / mauvais, plutôt que les individus qui peuvent être en désaccord avec l'une ou l'autre de ces opinions dans l'article du blog).

Danny Tuppeny
la source
1
Salut Dan! Votre aditude est inspirante. Outre la plate-forme .NET (et haskell), je vous encourage à regarder scala. Debashish ghosh a écrit quelques blogs sur la modélisation de domaine avec des outils fonctionnels. C'était perspicace pour moi. J'espère pour vous aussi. Voilà
AndreasScheinert
2
Un de mes collègues m'a envoyé aujourd'hui un article de blog intéressant: blog.inf.ed.ac.uk/sapm/2014/02/04/… Il semble que les gens commencent à contester l'idée que les modèles de domaine anémique sont carrément mauvais; que je pense pourrait être une bonne chose!
Danny Tuppeny
1
Le billet de blog que vous avez mentionné est basé sur une idée erronée: "Normalement, les données sont exposées sans être encapsulées. Les données sont immuables, elles ne peuvent donc pas être" endommagées "par une fonction défectueuse." Même les types immuables ont des invariants qui doivent être préservés, ce qui nécessite de cacher des données et de contrôler comment elles peuvent être créées. Par exemple, vous ne pouvez pas exposer l'implémentation d'un arbre rouge-noir immuable, car quelqu'un pourrait alors créer un arbre composé uniquement de nœuds rouges.
Doval
4
Pour être honnête, @Doval revient à dire que vous ne pouvez pas exposer un éditeur de système de fichiers car quelqu'un pourrait remplir votre disque. Quelqu'un qui crée une arborescence composée uniquement de nœuds rouges n'endommage absolument pas l'arborescence rouge-noire à partir de laquelle ils ont été clonés, ni aucun code dans le système qui utilise cette instance bien formée. Si vous écrivez du code qui crée activement de nouvelles occurrences de déchets ou fait des choses dangereuses, l'immuabilité ne vous sauvera pas, mais sauvera les autres de vous . Cacher l'implémentation n'empêchera pas les gens d'écrire du code sans signification qui finit par diviser par zéro.
Jimmy Hoffa
2
@ JimmyHoffa Je suis d'accord, mais cela n'a rien à voir avec ce que j'ai critiqué. L'auteur affirme qu'il est normalement normal que les données soient exposées parce qu'elles sont immuables, et je dis que l'immutabilité ne supprime pas par magie la nécessité de masquer les détails de la mise en œuvre.
Doval

Réponses:

37

La raison principale pour laquelle FP cherche à atteindre cet objectif, contrairement à C # OOP, c’est que dans ce dernier, l’accent est mis sur la transparence référentielle; c'est-à-dire que les données vont dans une fonction et que les données sortent, mais les données d'origine ne sont pas modifiées.

Dans C # OOP, il existe un concept de délégation de responsabilité selon lequel vous déléguez la gestion d'un objet à celui-ci et vous souhaitez donc que celui-ci modifie ses propres internes.

Dans FP, vous ne voulez jamais changer les valeurs d'un objet. Par conséquent, l'intégration de vos fonctions dans votre objet n'a pas de sens.

Plus loin dans FP, vous avez un polymorphisme plus élevé, ce qui permet à vos fonctions d’être beaucoup plus généralisées que COP ne le permet. De cette façon, vous pouvez écrire une fonction qui fonctionne pour tout a, et donc l’incorporer dans un bloc de données n’a aucun sens; cela couplerait étroitement la méthode pour qu’elle ne fonctionne que avec ce type particulier de a. Un tel comportement est très courant dans C # OOP car vous n’avez pas la capacité d’abstraire des fonctions aussi généralement de toute façon, mais en PF, c’est un compromis.

Le plus gros problème que j'ai rencontré dans les modèles de domaine anémique dans C # OOP est que vous vous retrouvez avec du code en double car vous avez DTO x et 4 fonctions différentes qui engagent l'activité f dans DTO x car 4 personnes différentes n'ont pas vu l'autre implémentation. . Lorsque vous mettez la méthode directement sur DTO x, ces 4 personnes voient toutes la mise en oeuvre de f et la réutilisent.

Les modèles de données anémiques dans C # OOP empêchent la réutilisation du code, mais ce n'est pas le cas dans FP car une seule fonction est généralisée à travers autant de types différents que vous obtenez une plus grande réutilisation du code car cette fonction est utilisable dans beaucoup plus de scénarios qu'une fonction que vous utilisez. écrirait pour un seul DTO en C #.


Comme indiqué dans les commentaires , l'inférence de type est l'un des avantages sur lesquels s'appuie FP pour permettre un polymorphisme aussi important, et vous pouvez en particulier le retrouver dans le système de type Hindley Milner avec l'inférence de type de l'algorithme W; une telle inférence de type dans le système de types C # OOP a été évitée car le temps de compilation lorsque l'ajout d'une inférence basée sur des contraintes devient extrêmement long en raison de la recherche exhaustive nécessaire, voir les détails ici: https://stackoverflow.com/questions/3968834/generics-why -cant-the-compiler-infer-the-type-arguments-in-this-case

Jimmy Hoffa
la source
Quelle (s) fonctionnalité (s) dans F # facilitent l'écriture de ce type de code réutilisable? Pourquoi le code en C # ne peut-il pas être réutilisé? (Je pense avoir vu la possibilité de faire en sorte que les méthodes prennent des arguments avec des propriétés spécifiques, sans avoir besoin d'une interface; ce serait un élément clé?)
Danny Tuppeny Le
7
@ DannyTuppeny honnêtement, F # est un mauvais exemple de comparaison, il s’agit simplement d’un C # légèrement habillé; c'est un langage impératif mutable, tout comme C #, il possède quelques installations de PF que C # n'a pas, mais pas beaucoup. Regardez assez pour voir où FP se démarque vraiment et des choses comme cela deviennent beaucoup plus possibles en raison des classes de types ainsi que des ADT génériques
Jimmy Hoffa Le
@ MattFenwick, je fais explicitement référence à C # car c'est ce que proposait l'affiche. Lorsque je fais référence à la programmation orientée objet tout au long de ma réponse, je parle ici de la programmation orientée objet, je vais la modifier pour la clarifier.
Jimmy Hoffa
2
Une caractéristique commune parmi les langages fonctionnels permettant ce type de réutilisation est le typage dynamique ou inféré. Bien que le langage lui-même puisse utiliser des types de données bien définis, la fonction typique ne se soucie pas de la nature des données, tant que les opérations (autres fonctions ou arithmétiques) qui y sont effectuées sont valides. Ceci est également disponible dans les paradigmes OO (Go, par exemple, a une implémentation d'interface implicite permettant à un objet d'être un canard, car il peut voler, nager et se quack, sans que l'objet ait été explicitement déclaré comme étant un canard), mais il l'est toujours. à peu près une condition requise de la programmation fonctionnelle.
KeithS
4
@KeithS Par surcharge basée sur la valeur dans Haskell, je pense que vous entendez une correspondance de motif. La capacité de Haskell d’avoir plusieurs fonctions de niveau supérieur du même nom avec des motifs différents disparaît immédiatement en une fonction de niveau supérieur + une correspondance de motif.
Jozefg
6

Pourquoi un modèle de domaine anémique est-il considéré comme mauvais en C # / OOP, mais très important en F # / FP?

Votre question a un gros problème qui limitera l'utilité des réponses que vous obtenez: vous sous-entendez / supposez que F # et FP sont similaires. La PF est une immense famille de langues comprenant la réécriture de termes symboliques, dynamique et statique. Même parmi les langages de FP statiques, il existe de nombreuses technologies différentes pour exprimer des modèles de domaine, tels que les modules d'ordre supérieur dans OCaml et SML (qui n'existent pas en F #). F # est l'un de ces langages fonctionnels, mais il est particulièrement remarquable pour sa maigreur et, en particulier, ne fournit pas de modules d'ordre supérieur ni de types de type supérieur.

En fait, je ne pourrais pas commencer à vous dire comment les modèles de domaine sont exprimés dans FP. L’autre réponse ici parle très précisément de la façon dont cela se fait en Haskell et ne s’applique pas du tout à Lisp (la mère de toutes les langues de la PF), à la famille de langues ML ou à toute autre langue fonctionnelle.

comment se fait-il que nous n'empruntions pas l'idée de séparer les données / le comportement, et même de le considérer comme mauvais?

Les génériques peuvent être considérés comme un moyen de séparer les données et les comportements. Les génériques de la famille ML de langages de programmation fonctionnels ne font pas partie de la programmation orientée objet. C # a des génériques, bien sûr. On pourrait donc soutenir que C # emprunte lentement l’idée de la séparation des données et du comportement.

Est-ce simplement que la définition ne correspond pas à la POO,

Je pense que la programmation orientée objet repose sur une prémisse fondamentalement différente et, par conséquent, ne vous donne pas les outils nécessaires pour séparer les données et le comportement. À toutes fins utiles, vous avez besoin des types de données produit et somme et de leur envoi. En ML, cela signifie des types d'union et d'enregistrement et une correspondance de modèle.

Découvrez l'exemple que j'ai donné ici .

ou y a-t-il une raison concrète pour laquelle il est mauvais en C # que, pour une raison quelconque, ne s'applique pas en F # (et est en fait inversé)?

Faites attention à ne pas sauter de la POO au C #. C # est loin d’être aussi puritain en matière de POO que d’autres langues. Le .NET Framework regorge maintenant de génériques, de méthodes statiques et même de lambdas.

(Remarque: je suis particulièrement intéressé par les différences de C # / F # qui pourraient changer l'opinion de ce qui est bon / mauvais, plutôt que les individus qui peuvent être en désaccord avec l'une ou l'autre de ces opinions dans l'article du blog).

L'absence de types d'union et de correspondance de modèle en C # rend la tâche presque impossible. Quand tout ce que vous avez est un marteau, tout ressemble à un clou ...

Jon Harrop
la source
2
... quand tout ce que vous avez est un marteau, les gens POO créeront des usines de marteaux; P +1 pour vraiment cerner le noeud de ce qui manque en C # pour permettre aux données et au comportement d'être complètement abstraits de l'autre: Types d'union et correspondance de modèle.
Jimmy Hoffa
-4

Je pense que dans une application métier, vous ne voulez souvent pas cacher les données, car la correspondance de modèle sur des valeurs immuables est très utile pour garantir que vous couvrez tous les cas possibles. Mais si vous implémentez des algorithmes complexes ou des structures de données, il vaut mieux cacher les détails de cette implémentation en transformant les ADT (types de données algébriques) en ADT (types de données abstraits).

Giacomo Citi
la source
4
Pourriez-vous expliquer un peu plus comment cela s'applique à la programmation orientée objet par rapport à la programmation fonctionnelle?