Ma question concerne un cas particulier de la super classe Animal.
- Ma
Animal
boîtemoveForward()
eteat()
. Seal
s'étendAnimal
.Dog
s'étendAnimal
.- Et il y a une créature spéciale qui s'étend également
Animal
appeléeHuman
. Human
implémente également une méthodespeak()
(non implémentée parAnimal
).
Dans une implémentation d'une méthode abstraite qui accepte, Animal
je voudrais utiliser la speak()
méthode. Cela ne semble pas possible sans faire un abattage. Jeremy Miller a écrit dans son article qu'une odeur baissée sent.
Quelle serait une solution pour éviter la rétrogradation dans cette situation?
java
object-oriented
type-casting
Bart Weber
la source
la source
Réponses:
Si vous avez une méthode qui doit savoir si la classe spécifique est de type
Human
afin de faire quelque chose, alors vous brisez certains principes SOLIDES , en particulier:À mon avis, si votre méthode attend un type de classe particulier, pour appeler sa méthode particulière, changez cette méthode pour n'accepter que cette classe, et non son interface.
Quelque chose comme ça :
et pas comme ça:
la source
Animal
appeléecanSpeak
et chaque implémentation concrète doit définir si elle peut ou non "parler".public void makeAnimalDoDailyThing(Animal animal) {animal.moveForward(); animal.eat()}
etpublic void makeAnimalDoDailyThing(Human human) {human.moveForward(); human.eat(); human.speak();}
Le problème n'est pas que vous abaissez - c'est que vous abattez
Human
. À la place, créez une interface:De cette façon, la condition n'est pas que l'animal soit
Human
- la condition est qu'il puisse parler. Cela signifie qu'ilmysteriousMethod
peut fonctionner avec d'autres sous-classes non humainesAnimal
tant qu'elles sont implémentéesCanSpeak
.la source
Animal
et que tous les utilisateurs de cette méthode contiendront l'objet qu'ils souhaitent lui envoyer via uneCanSpeak
référence de type (ou mêmeHuman
une référence de type). Si tel était le cas, cette méthode aurait pu être utiliséeHuman
en premier lieu et nous n'aurions pas besoin de la présenterCanSpeak
.CanSpeak
en premier lieu n'est pas que nous avons quelque chose qui l'implémente (Human
) mais que nous avons quelque chose qui l'utilise (la méthode). IlCanSpeak
s'agit de découpler cette méthode de la classe concrèteHuman
. Si nous n'avions pas de méthodes pour traiter les chosesCanSpeak
différemment, il serait inutile de les distinguerCanSpeak
. Nous ne créons pas une interface juste parce que nous avons une méthode ...Vous pouvez ajouter Communiquer à l'animal. Le chien aboie, l'homme parle, le phoque ... euh ... Je ne sais pas ce que fait le phoque.
Mais il semble que votre méthode soit conçue pour (Animal est humain) parler ();
La question que vous voudrez peut-être poser est quelle est l'alternative? C'est difficile de donner une suggestion car je ne sais pas exactement ce que vous voulez réaliser. Il existe des situations théoriques où le downcasting / upcasting est la meilleure approche.
la source
Dans ce cas, l'implémentation par défaut de
speak()
dans laAbstractAnimal
classe serait:À ce stade, vous avez une implémentation par défaut dans la classe Abstract - et elle se comporte correctement.
Oui, cela signifie que vous avez des tentatives de captures éparpillées dans le code pour gérer tout
speak
, mais l'alternative à cela consiste àif(thingy is Human)
envelopper tous les discours à la place.L'avantage de l'exception est que si vous avez un autre type de chose qui parle (un perroquet), vous n'aurez pas besoin de réimplémenter tous vos tests.
la source
canSpeak()
méthode pour mieux gérer cela.La descente est parfois nécessaire et appropriée. En particulier, cela est souvent approprié dans les cas où l'on a des objets qui peuvent ou non avoir une certaine capacité, et on souhaite utiliser cette capacité lorsqu'elle existe tout en manipulant des objets sans cette capacité d'une manière par défaut. À titre d'exemple simple, supposons que l'
String
on demande à a s'il est égal à un autre objet arbitraire. Pour que l'un soitString
égal à un autreString
, il doit examiner la longueur et le tableau de caractères de support de l'autre chaîne. SiString
on demande à a s'il est égal à aDog
, cependant, il ne peut pas accéder à la longueur deDog
, mais il ne devrait pas avoir à le faire; au lieu de cela, si l'objet auquel aString
est censé se comparer n'est pas unString
, la comparaison doit utiliser un comportement par défaut (signalant que l'autre objet n'est pas égal).Le moment où la descente doit être considéré comme le plus douteux est celui où l'objet projeté est "connu" pour être du type approprié. En général, si un objet est connu pour être un
Cat
, il faut utiliser une variable de typeCat
, plutôt qu'une variable de typeAnimal
, pour s'y référer. Il y a des moments où cela ne fonctionne pas toujours, cependant. Par exemple, uneZoo
collection peut contenir des paires d'objets dans des emplacements de tableau pairs / impairs, en s'attendant à ce que les objets de chaque paire puissent agir les uns sur les autres, même s'ils ne peuvent pas agir sur les objets des autres paires. Dans un tel cas, les objets de chaque paire devraient toujours accepter un type de paramètre non spécifique de sorte qu'ils pourraient, syntaxiquement , transmettre les objets de toute autre paire. Ainsi, même siCat
l »playWith(Animal other)
La méthode ne fonctionnerait que lorsqueother
était unCat
, leZoo
devrait avoir la possibilité de lui passer un élément d'unAnimal[]
, donc son type de paramètre devrait êtreAnimal
plutôt queCat
.Dans les cas où la descente est légitimement inévitable, il faut l'utiliser sans scrupule. La question clé est de déterminer quand on peut sensiblement éviter la descente, et de l'éviter quand c'est raisonnablement possible.
la source
Object.equalToString(String string)
. Ensuite, vous n'avezboolean String.equal(Object object) { return object.equalStoString(this); }
donc pas besoin de downcast: vous pouvez utiliser la répartition dynamique.Object
avoir deequalStoString
méthode virtuelle, et je dois admettre que je ne sais pas comment l'exemple cité fonctionnerait même en Java, mais en C #, la répartition dynamique (par opposition à la répartition virtuelle) signifierait que le compilateur a essentiellement pour effectuer une recherche de nom basée sur la réflexion la première fois qu'une méthode est utilisée sur une classe, qui est distincte de la répartition virtuelle (qui effectue simplement un appel via un emplacement dans la table de méthode virtuelle qui doit contenir une adresse de méthode valide).Vous avez quelques choix:
Utilisez la réflexion pour appeler
speak
si elle existe. Avantage: pas de dépendanceHuman
. Inconvénient: il existe désormais une dépendance cachée sur le nom "speak".Introduisez une nouvelle interface
Speaker
et abaissez l'interface. Ceci est plus flexible qu'en fonction d'un type de béton spécifique. Il présente l'inconvénient que vous devez modifierHuman
pour l'implémenterSpeaker
. Cela ne fonctionnera pas si vous ne pouvez pas modifierHuman
Descendu à
Human
. Cela présente l'inconvénient que vous devrez modifier le code chaque fois que vous voulez qu'une autre sous-classe parle. Idéalement, vous souhaitez étendre les applications en ajoutant du code sans revenir en arrière et modifier l'ancien code.la source