Meine Frage betrifft einen Sonderfall der Superklasse Animal.
- Meine
Animal
DosemoveForward()
undeat()
. Seal
erstrecktAnimal
.Dog
erstrecktAnimal
.- Und es gibt ein besonderes Wesen , das auch erstreckt
Animal
genanntHuman
. Human
implementiert auch eine Methodespeak()
(nicht implementiert vonAnimal
).
In einer Implementierung einer abstrakten Methode, die akzeptiert, Animal
möchte ich die speak()
Methode verwenden. Das scheint ohne Niedergeschlagenheit nicht möglich zu sein. Jeremy Miller schrieb in seinem Artikel, dass ein Niedergeschlagener riecht.
Was wäre eine Lösung, um in dieser Situation einen Sturz zu vermeiden?
java
object-oriented
type-casting
Bart Weber
quelle
quelle
Antworten:
Wenn Sie eine Methode haben, die wissen muss, ob die bestimmte Klasse vom Typ ist
Human
, um etwas zu tun, dann brechen Sie einige SOLID-Prinzipien , insbesondere:Wenn Ihre Methode einen bestimmten Klassentyp erwartet, um diese bestimmte Methode aufzurufen, ändern Sie diese Methode meiner Meinung nach so, dass nur diese Klasse und nicht die Schnittstelle akzeptiert wird.
Etwas wie das :
und nicht so:
quelle
Animal
rufencanSpeak
und jede konkrete Implementierung muss definieren, ob sie "sprechen" kann oder nicht.public void makeAnimalDoDailyThing(Animal animal) {animal.moveForward(); animal.eat()}
undpublic void makeAnimalDoDailyThing(Human human) {human.moveForward(); human.eat(); human.speak();}
Das Problem ist nicht, dass du einen Downcast machst - es ist, dass du einen Downcast machst
Human
. Erstellen Sie stattdessen eine Schnittstelle:Auf diese Weise ist die Bedingung nicht, dass das Tier ist
Human
- die Bedingung ist, dass es sprechen kann. Dies bedeutetmysteriousMethod
, dass mit anderen, nicht menschlichen Unterklassen gearbeitet werden kann,Animal
sofern diese implementiert sindCanSpeak
.quelle
Animal
und alle Benutzer dieser Methode enthalten das Objekt, das sie über eineCanSpeak
Typreferenz (oder sogar eine Typreferenz) an sie senden möchtenHuman
. Wenn das der Fall wäre, hätte diese MethodeHuman
in erster Linie verwendet werden können und wir müssten sie nicht vorstellenCanSpeak
.CanSpeak
haben, ist nicht, dass wir etwas haben, das es implementiert (Human
), sondern dass wir etwas haben, das es verwendet (die Methode). EsCanSpeak
geht darum, diese Methode von der konkreten Klasse zu entkoppelnHuman
. Wenn wir keine Methoden hätten, um Dinge soCanSpeak
unterschiedlich zu behandeln , hätte es keinen Sinn, Dinge so zu unterscheidenCanSpeak
. Wir erstellen keine Schnittstelle, nur weil wir eine Methode haben ...In diesem Fall wäre die Standardimplementierung von
speak()
in derAbstractAnimal
Klasse:Zu diesem Zeitpunkt haben Sie eine Standardimplementierung in der Abstract-Klasse - und sie verhält sich korrekt.
Ja, das heißt, Sie haben Versuchsfänge im Code verstreut, um alle zu behandeln
speak
, aber die Alternative dazu besteht darin,if(thingy is Human)
stattdessen alle Speaks zu verpacken.Der Vorteil der Ausnahme ist, dass Sie nicht alle Tests erneut durchführen müssen, wenn Sie irgendwann eine andere Art von Dingen haben, die sprechen (einen Papagei).
quelle
canSpeak()
Methode verwenden, um dies besser zu handhaben.Sie könnten Communicate to Animal hinzufügen. Hund bellt, Mensch spricht, Robbe ... äh ... Ich weiß nicht, was Robbe tut.
Aber es hört sich so an, als ob Ihre Methode so konzipiert ist, dass (Tier ist Mensch) Sprechen ();
Die Frage, die Sie möglicherweise stellen möchten, lautet: Was ist die Alternative? Es ist schwierig, einen Vorschlag zu machen, da ich nicht genau weiß, was Sie erreichen möchten. Es gibt theoretische Situationen, in denen Downcasting / Upcasting der beste Ansatz ist.
quelle
Downcasting ist manchmal notwendig und angemessen. Insbesondere ist es häufig in Fällen angebracht, in denen Objekte vorhanden sind, die möglicherweise über eine bestimmte Fähigkeit verfügen oder nicht, und diese Fähigkeit möchte man verwenden, wenn sie vorhanden ist, während Objekte ohne diese Fähigkeit auf eine bestimmte Standardweise behandelt werden. Als einfaches Beispiel wird angenommen, dass a
String
gefragt wird, ob es einem anderen beliebigen Objekt entspricht. Damit einsString
dem anderen entsprichtString
, muss es die Länge und das Hintergrundzeichenarray der anderen Zeichenfolge untersuchen. Wenn aString
gefragt wird, ob es gleich aDog
ist, kann es nicht auf die Länge von zugreifenDog
, aber es sollte nicht müssen; stattdessen, wenn das Objekt, mit dem sich aString
vergleichen soll, nicht a istString
sollte der Vergleich ein Standardverhalten verwenden (Meldung, dass das andere Objekt nicht gleich ist).Die Zeit, in der das Herabwerfen als am zweifelhaftesten angesehen werden sollte, ist die Zeit, in der "bekannt" ist, dass das zu gießende Objekt vom richtigen Typ ist. Wenn bekannt ist, dass ein Objekt ein Objekt ist
Cat
, sollte im Allgemeinen eine Variable vom TypCat
anstelle einer Variablen vom Typ verwendet werdenAnimal
, um auf dieses Objekt zu verweisen. Es gibt Zeiten, in denen dies jedoch nicht immer funktioniert. Beispielsweise kann eineZoo
Sammlung Paare von Objekten in Slots für gerade / ungerade Arrays enthalten, mit der Erwartung, dass die Objekte in jedem Paar aufeinander einwirken können, auch wenn sie nicht auf die Objekte in anderen Paaren einwirken können. In einem solchen Fall müssten die Objekte in jedem Paar immer noch einen unspezifischen Parametertyp akzeptieren, sodass sie syntaktisch die Objekte von jedem anderen Paar übergeben . Also, auch wennCat
's gehtplayWith(Animal other)
Verfahren funktionieren würde nur dann , wennother
eine warCat
, dieZoo
in der Lage sein müssen , wäre es ein Element eines zu übergebenAnimal[]
, so dass ihr Parametertyp sein müsste,Animal
als vielmehrCat
.In Fällen, in denen Downcasting legitimerweise unvermeidbar ist, sollte man es ohne Bedenken anwenden. Die entscheidende Frage ist, wann man Downcasting sinnvoll vermeiden und wann es sinnvoll möglich ist.
quelle
Object.equalToString(String string)
. Dann haben Sieboolean String.equal(Object object) { return object.equalStoString(this); }
also keinen Downcast nötig: Sie können dynamisches Dispatching nutzen.Object
es eineequalStoString
virtuelle Methode gibt, und ich gebe zu, dass ich nicht weiß, wie das angeführte Beispiel überhaupt in Java funktionieren würde, aber in C # würde dynamisches Versenden (anders als virtuelles Versenden) bedeuten, dass der Compiler im Wesentlichen über Folgendes verfügt Reflection-based Name-Lookup ausführen, wenn eine Methode zum ersten Mal für eine Klasse verwendet wird, die sich vom virtuellen Versand unterscheidet (der einfach über einen Steckplatz in der virtuellen Methodentabelle aufruft, der eine gültige Methodenadresse enthalten muss).Sie haben ein paar Möglichkeiten:
Verwenden Sie Reflection, um anzurufen,
speak
falls vorhanden. Vorteil: keine Abhängigkeit vonHuman
. Nachteil: Es gibt jetzt eine versteckte Abhängigkeit vom Namen "speak".Eine neue Schnittstelle einführen
Speaker
und auf die Schnittstelle herunterschalten. Dies ist flexibler als abhängig von einem bestimmten Betontyp. Dies hat den Nachteil, dass Sie Änderungen vornehmen müssen, um sieHuman
zu implementierenSpeaker
. Dies funktioniert nicht, wenn Sie keine Änderungen vornehmen könnenHuman
Niedergeschlagen auf
Human
. Dies hat den Nachteil, dass Sie den Code immer dann ändern müssen, wenn eine andere Unterklasse sprechen soll. Idealerweise möchten Sie Anwendungen erweitern, indem Sie Code hinzufügen, ohne immer wieder zurück zu gehen und alten Code zu ändern.quelle