La façon dont le problème de "modèle anémique" est décrit ne se traduit pas bien en PF en l'état. Tout d'abord, il doit être généralisé de manière appropriée. En son cœur, un modèle anémique est un modèle qui contient des connaissances sur la manière de l’utiliser correctement qui n’est pas encapsulé par le modèle lui-même. Au lieu de cela, ces connaissances sont réparties autour d’une pile de services connexes. Ces services ne devraient être que des clients du modèle, mais en raison de son anémie, ils en sont tenus responsables . Par exemple, considérons une Account
classe qui ne peut pas être utilisée pour activer ou désactiver des comptes ou même rechercher des informations sur un compte si elle n'est pas gérée via une AccountManager
classe. Le compte doit être responsable des opérations de base sur celui-ci, et non d'une classe de gestionnaire externe.
En programmation fonctionnelle, un problème similaire existe lorsque les types de données ne représentent pas exactement ce qu'ils sont censés modéliser. Supposons que nous devions définir un type représentant les ID utilisateur. Une définition "anémique" indiquerait que les ID utilisateur sont des chaînes. C'est techniquement faisable, mais pose d'énormes problèmes car les ID utilisateur ne sont pas utilisés comme des chaînes arbitraires. Cela n'a aucun sens de les concaténer ou d'en découper des sous-chaînes. Unicode ne devrait pas avoir d'importance, et ils devraient pouvoir être incorporés facilement dans des URL et d'autres contextes avec des limitations strictes de caractères et de format.
La résolution de ce problème se produit généralement en quelques étapes. Une première coupe simple consiste à dire "Eh bien, a UserID
est représenté de manière équivalente à une chaîne, mais ils sont de types différents et vous ne pouvez pas utiliser l'un de ceux que vous attendez de l'autre." Haskell (et quelques autres langages fonctionnels dactylographiés) fournit cette fonctionnalité via newtype
:
newtype UserID = UserID String
Ceci définit une UserID
fonction qui, lorsqu'elle reçoit une String
valeur, construit une valeur qui est traitée comme un UserID
système de types, mais qui reste juste String
à l'exécution. Désormais, les fonctions peuvent déclarer qu’elles nécessitent un à la UserID
place d’une chaîne; en utilisant UserID
s où vous utilisiez auparavant des chaînes, gardes contre le code concaténant deux UserID
s ensemble. Le système de types garantit que cela ne peut pas arriver, aucun test requis.
La faiblesse est que le code peut encore prendre toute arbitraire String
comme "hello"
et construire un UserID
de lui. La prochaine étape consiste à créer une fonction "constructeur intelligent" qui, lorsqu'elle reçoit une chaîne, vérifie certains invariants et ne les renvoie que UserID
s'ils sont satisfaits. Ensuite, le UserID
constructeur "dumb" est rendu privé. Par conséquent, si un client le souhaite, UserID
il doit utiliser le constructeur intelligent, empêchant ainsi la création d’identificateurs d’utilisateur mal formés.
Même les étapes suivantes définissent le UserID
type de données de telle sorte qu'il est impossible d'en construire un qui est mal formé ou "impropre", par simple définition. Par exemple, définir UserID
une liste de chiffres:
data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]
Pour construire une UserID
liste de chiffres doit être fournie. Compte tenu de cette définition, il est trivial de montrer qu'il est impossible UserID
d'exister sans pouvoir être représenté dans une URL. Définir de tels modèles de données dans Haskell est souvent facilité par des fonctionnalités de système de types avancées telles que les types de données et les types de données algébriques généralisées (GADT) , qui permettent au système de types de définir et de prouver davantage d'invariants dans votre code. Lorsque les données sont découplées du comportement, votre définition de données est le seul moyen dont vous disposez pour appliquer le comportement.