Eh bien, il semble que votre domaine sémantique ait une relation IS-A, mais vous vous méfiez un peu de l'utilisation de sous-types / héritage pour modéliser cela, en particulier en raison de la réflexion du type d'exécution. Je pense cependant que vous avez peur de la mauvaise chose - le sous-typage présente en effet des dangers, mais le fait que vous interrogiez un objet à l'exécution n'est pas le problème. Vous verrez ce que je veux dire.
La programmation orientée objet s'est appuyée assez fortement sur la notion de relations IS-A, elle s'est sans doute trop appuyée sur elle, conduisant à deux célèbres concepts critiques:
Mais je pense qu'il existe une autre façon, plus basée sur la programmation fonctionnelle, d'examiner les relations IS-A qui n'a peut-être pas ces difficultés. Tout d'abord, nous voulons modéliser les chevaux et les licornes dans notre programme, nous allons donc avoir un Horse
et un Unicorn
type. Quelles sont les valeurs de ces types? Eh bien, je dirais ceci:
- Les valeurs de ces types sont des représentations ou des descriptions de chevaux et de licornes (respectivement);
- Ce sont des représentations ou descriptions schématisées - elles ne sont pas de forme libre, elles sont construites selon des règles très strictes.
Cela peut sembler évident, mais je pense que l'une des façons dont les gens abordent des problèmes comme le problème du cercle-ellipse est de ne pas s'occuper de ces points avec suffisamment d'attention. Chaque cercle est une ellipse, mais cela ne signifie pas que chaque description schématisée d'un cercle est automatiquement une description schématisée d'une ellipse selon un schéma différent. En d'autres termes, ce n'est pas parce qu'un cercle est une ellipse que a Circle
est un Ellipse
, pour ainsi dire. Mais cela signifie que:
- Il existe une fonction totale qui convertit n'importe quelle
Circle
(description de cercle schématisée) en Ellipse
(type de description différent) qui décrit les mêmes cercles;
- Il existe une fonction partielle qui prend un
Ellipse
et, si décrit un cercle, renvoie le correspondant Circle
.
Donc, en termes de programmation fonctionnelle, votre Unicorn
type n'a pas besoin d'être un sous-type du Horse
tout, vous avez juste besoin d'opérations comme celles-ci:
-- Convert any unicorn-description of into a horse-description that
-- describes the same unicorns.
toHorse :: Unicorn -> Horse
-- If the horse described by the given horse-description is a unicorn,
-- then return a unicorn-description of that unicorn, otherwise return
-- nothing.
toUnicorn :: Horse -> Maybe Unicorn
Et toUnicorn
doit être un inverse droit de toHorse
:
toUnicorn (toHorse x) = Just x
Le Maybe
type de Haskell est ce que les autres langues appellent un type "option". Par exemple, le Optional<Unicorn>
type Java 8 est un Unicorn
ou rien. Notez que deux de vos alternatives - lever une exception ou renvoyer une «valeur par défaut ou magique» - sont très similaires aux types d'options.
Donc, fondamentalement, ce que j'ai fait ici est de reconstruire le concept IS-A en termes de types et de fonctions, sans utiliser de sous-types ou d'héritage. Ce que j'en retiendrais, c'est:
- Votre modèle doit avoir un
Horse
type;
- Le
Horse
type doit coder suffisamment d'informations pour déterminer sans ambiguïté si une valeur décrit une licorne;
- Certaines opérations du
Horse
type doivent exposer ces informations afin que les clients du type puissent observer si un donné Horse
est une licorne;
- Les clients du
Horse
type devront utiliser ces dernières opérations lors de l'exécution pour distinguer les licornes et les chevaux.
Il s'agit donc fondamentalement d'un Horse
modèle "demandez à tous s'il s'agit d'une licorne". Vous vous méfiez de ce modèle, mais je le pense à tort. Si je vous donne une liste de Horse
s, tout ce que le type garantit est que les choses que les éléments de la liste décrivent sont des chevaux - vous devrez donc inévitablement faire quelque chose au moment de l'exécution pour dire lesquels sont des licornes. Il n'y a donc pas moyen de contourner cela, je pense - vous devez mettre en œuvre des opérations qui le feront pour vous.
Dans la programmation orientée objet, la façon habituelle de procéder est la suivante:
- Ayez un
Horse
type;
- Avoir
Unicorn
comme sous-type de Horse
;
- Utilisez la réflexion du type d'exécution comme opération accessible au client qui discerne si un donné
Horse
est un Unicorn
.
Cela a une grande faiblesse, lorsque vous le regardez sous l'angle "chose vs description" que j'ai présenté ci-dessus:
- Et si vous avez une
Horse
instance qui décrit une licorne mais n'est pas une Unicorn
instance?
Pour en revenir au début, c'est ce que je pense être la partie vraiment effrayante de l'utilisation du sous-typage et des downcasts pour modéliser cette relation IS-A - pas le fait que vous devez faire une vérification de l'exécution. Abuser un peu de la typographie, demander Horse
si c'est une Unicorn
instance n'est pas synonyme de demander Horse
si c'est une licorne (si c'est une Horse
description d'un cheval qui est aussi une licorne). Sauf si votre programme a fait de grands efforts pour encapsuler le code qui construit de Horses
sorte que chaque fois qu'un client essaie de construire un Horse
qui décrit une licorne, la Unicorn
classe est instanciée. D'après mon expérience, les programmeurs font rarement cela avec soin.
Je choisirais donc l'approche où il y a une opération explicite et non abattue qui convertit Horse
s en Unicorn
s. Il peut s'agir d'une méthode du Horse
type:
interface Horse {
// ...
Optional<Unicorn> toUnicorn();
}
... ou ce pourrait être un objet extérieur (votre "objet séparé sur un cheval qui vous indique si le cheval est une licorne ou non"):
class HorseToUnicornCoercion {
Optional<Unicorn> convert(Horse horse) {
// ...
}
}
Le choix entre ceux-ci dépend de la façon dont votre programme est organisé - dans les deux cas, vous avez l'équivalent de mon Horse -> Maybe Unicorn
opération d'en haut, vous ne faites que l'empaqueter de différentes manières (ce qui aura certainement des effets d'entraînement sur les opérations dont le Horse
type a besoin exposer à ses clients).