Un inconvénient bien connu des hiérarchies de classes traditionnelles est qu'elles sont mauvaises en ce qui concerne la modélisation du monde réel. Par exemple, essayer de représenter les espèces animales avec des classes. Il y a en fait plusieurs problèmes en faisant cela, mais celui auquel je n'ai jamais vu de solution est quand une sous-classe "perd" un comportement ou une propriété qui a été définie dans une super-classe, comme un pingouin ne pouvant pas voler (il y a sont probablement de meilleurs exemples, mais c'est le premier qui me vient à l'esprit).
D'une part, vous ne voulez pas définir, pour chaque propriété et comportement, un indicateur qui spécifie s'il est présent, et le vérifier à chaque fois avant d'accéder à ce comportement ou à cette propriété. Vous voulez juste dire que les oiseaux peuvent voler, simplement et clairement, dans la classe Bird. Mais alors ce serait bien si on pouvait définir des "exceptions" par la suite, sans avoir à utiliser des horribles hacks partout. Cela se produit souvent lorsqu'un système est productif depuis un certain temps. Vous trouvez soudain une «exception» qui ne correspond pas du tout à la conception d'origine, et vous ne voulez pas modifier une grande partie de votre code pour l'adapter.
Donc, y a-t-il des modèles de langage ou de conception qui peuvent gérer proprement ce problème, sans nécessiter de changements majeurs à la "super-classe", et tout le code qui l'utilise? Même si une solution ne gère qu'un cas spécifique, plusieurs solutions peuvent former ensemble une stratégie complète.
Après réflexion, je me rends compte que j'ai oublié le principe de substitution de Liskov. Voilà pourquoi vous ne pouvez pas le faire. En supposant que vous définissiez des "traits / interfaces" pour tous les "groupes d'entités" principaux, vous pouvez librement implémenter des traits dans différentes branches de la hiérarchie, comme le trait Volant pourrait être implémenté par Birds, et certains types spéciaux d'écureuils et de poissons.
Donc, ma question pourrait se résumer à "Comment pourrais-je annuler la mise en œuvre d'un trait?" Si votre super-classe est un Java Serializable, vous devez l'être aussi, même s'il n'y a aucun moyen pour vous de sérialiser votre état, par exemple si vous contenez un "Socket".
Une façon de le faire est de toujours définir tous vos traits par paires dès le début: Flying et NotFlying (ce qui lèverait UnsupportedOperationException, s'il n'est pas vérifié). Le Not-trait ne définirait aucune nouvelle interface et pourrait être simplement vérifié. Cela ressemble à une solution "bon marché", en particulier si elle est utilisée dès le départ.
" it would be nice if one could define "exceptions" afterward, without having to use some horrible hacks everywhere"
considérez-vous une méthode d'usine contrôlant le comportement hacky?
NotSupportedException
de Penguin.fly()
.
class Penguin < Bird; undef fly; end;
. Si vous devez ou non est une autre question.
function save_yourself_from_crashing_airplane(Bird b) { f.fly() }
deviendrait beaucoup plus compliqué. (comme Peter Török l'a dit, cela viole le LSP)