Moje pytanie dotyczy specjalnego przypadku superklasy Animal.
- Moja
Animal
puszkamoveForward()
ieat()
. Seal
rozszerza sięAnimal
.Dog
rozszerza sięAnimal
.- I jest specjalne stworzenie, które rozszerza także
Animal
tzwHuman
. Human
implementuje również metodęspeak()
(nie zaimplementowaną przezAnimal
).
W implementacji metody abstrakcyjnej, która akceptuje Animal
, chciałbym użyć tej speak()
metody. To nie wydaje się możliwe bez zrujnowania. Jeremy Miller napisał w swoim artykule, że pachnie przygnębiony.
Jakie byłoby rozwiązanie, aby uniknąć downcastingu w tej sytuacji?
java
object-oriented
type-casting
Bart Weber
źródło
źródło
Odpowiedzi:
Jeśli masz metodę, która musi wiedzieć, czy konkretna klasa jest typu
Human
, aby coś zrobić, to łamiesz niektóre zasady SOLID , w szczególności:Moim zdaniem, jeśli twoja metoda oczekuje określonego typu klasy, aby wywołać tę metodę, zmień tę metodę, aby akceptowała tylko tę klasę, a nie interfejs.
Coś takiego :
i nie tak:
źródło
Animal
nazwałcanSpeak
a każda realizacja beton musi określić, czy może „mówić”.public void makeAnimalDoDailyThing(Animal animal) {animal.moveForward(); animal.eat()}
ipublic void makeAnimalDoDailyThing(Human human) {human.moveForward(); human.eat(); human.speak();}
Problemem nie jest to, że jesteś downcastingiem - to, że jesteś downcastingiem
Human
. Zamiast tego utwórz interfejs:W ten sposób warunkiem nie jest to, że zwierzę jest
Human
- warunkiem jest to, że może mówić. Oznacza to, żemysteriousMethod
mogą współpracować z innymi niekluczowymi podklasami, oAnimal
ile tylko się zaimplementująCanSpeak
.źródło
Animal
i że wszyscy użytkownicy tej metody będą przechowywać obiekt, który chcą do niej wysłać za pośrednictwemCanSpeak
odwołania do typu (lub nawetHuman
odwołania do typu). Gdyby tak było, metoda ta mogłaby zostać zastosowanaHuman
w pierwszej kolejności i nie musielibyśmy jej przedstawiaćCanSpeak
.CanSpeak
przede wszystkim, nie jest to, że mamy coś, co ją implementuje (Human
), ale że mamy coś, co z niej korzysta (metoda). Chodzi oCanSpeak
to, aby oddzielić tę metodę od konkretnej klasyHuman
. Gdybyśmy nie mieli metod, które traktowałyby te rzeczyCanSpeak
inaczej, nie byłoby sensu rozróżniać tych rzeczyCanSpeak
. Nie tworzymy interfejsu tylko dlatego, że mamy metodę ...Możesz dodać Communicate to Animal. Pies szczeka, Człowiek mówi, Pieczęć ... Uch ... Nie wiem, co robi pieczęć.
Ale wygląda na to, że twoja metoda została zaprojektowana, jeśli (Animal is Human) Speak ();
Pytanie, które możesz zadać, brzmi: jaka jest alternatywa? Trudno podać sugestię, ponieważ nie wiem dokładnie, co chcesz osiągnąć. Istnieją teoretyczne sytuacje, w których downcasting / upcasting jest najlepszym podejściem.
źródło
W takim przypadku domyślną implementacją
speak()
wAbstractAnimal
klasie będzie:W tym momencie masz domyślną implementację w klasie Abstract - i działa ona poprawnie.
Tak, oznacza to, że masz rozrzuty rozrzucone po kodzie, aby obsłużyć każdy
speak
, ale alternatywą jestif(thingy is Human)
owijanie wszystkich wypowiedzi.Zaletą tego wyjątku jest to, że jeśli masz coś innego, co mówi (papuga), nie będziesz musiał zaimplementować wszystkich swoich testów.
źródło
canSpeak()
metody, aby lepiej sobie z tym poradzić.Downcasting jest czasem konieczny i odpowiedni. W szczególności jest to często odpowiednie w przypadkach, w których ktoś posiada przedmioty, które mogą, ale nie muszą, mieć jakąś zdolność, i chce skorzystać z tej zdolności, gdy istnieje, podczas obsługi obiektów bez tej zdolności w pewien domyślny sposób. Jako prosty przykład, załóżmy, że a
String
jest pytane, czy jest ono równe jakimś innemu dowolnemu obiektowi. Aby jedenString
był równyString
, musi zbadać długość i tylną tablicę znaków drugiego łańcucha. Jeśli aString
zostanie zapytane, czy jest równe aDog
, nie może uzyskać dostępu do długościDog
, ale nie powinno tak być; zamiast tego, jeśli obiekt, do któregoString
ma się porównać a, nie jestString
, porównanie powinno wykorzystywać zachowanie domyślne (zgłaszanie, że drugi obiekt nie jest równy).Moment, w którym spuszczanie wody należy uznać za najbardziej wątpliwe, to moment, w którym rzucany przedmiot jest „znany” z właściwego rodzaju. Ogólnie rzecz biorąc, jeśli obiekt jest znany jako
Cat
, należy do niego użyć zmiennej typuCat
, a nie zmiennej typuAnimal
. Są jednak chwile, kiedy to nie zawsze działa. Na przykładZoo
kolekcja może przechowywać pary obiektów w parzystych / nieparzystych gniazdach tablicowych, oczekując, że obiekty w każdej parze będą mogły na siebie oddziaływać, nawet jeśli nie będą mogły oddziaływać na obiekty w innych parach. W takim przypadku obiekty w każdej parze nadal będą musiały zaakceptować nieokreślony typ parametru, tak że mogłyby składniowo przekazać obiekty z dowolnej innej pary. Tak więc, nawet jeśliCat
„splayWith(Animal other)
metoda działałaby tylko wtedy, gdyother
była aCat
,Zoo
musiałaby móc przekazać jej element anAnimal[]
, więc typem parametru musiałaby byćAnimal
zamiastCat
.W przypadkach, gdy downcasting jest prawnie nieunikniony, należy go używać bez żadnych skrupułów. Kluczowym pytaniem jest ustalenie, kiedy można rozsądnie uniknąć upuszczenia i unikanie go, gdy jest to rozsądnie możliwe.
źródło
Object.equalToString(String string)
. Więc maszboolean String.equal(Object object) { return object.equalStoString(this); }
Tak, nie jest potrzebny downcast: możesz użyć dynamicznej wysyłki.Object
, abyequalStoString
istniała żadna metoda wirtualna i przyznaję, że nie wiem, jak cytowany przykład mógłby nawet działać w Javie, ale w języku C # dynamiczne wysyłanie (w odróżnieniu od wirtualnego wysyłania) oznaczałoby, że kompilator zasadniczo ma aby wykonać wyszukiwanie nazw oparte na odbiciu przy pierwszym użyciu metody w klasie, która różni się od wirtualnej wysyłki (która po prostu wykonuje wywołanie przez szczelinę w tabeli metod wirtualnych, która musi zawierać prawidłowy adres metody).Masz kilka możliwości:
Użyj odbicia, aby zadzwonić,
speak
jeśli istnieje. Zaleta: brak zależności odHuman
. Wada: teraz istnieje ukryta zależność od nazwy „mówić”.Wprowadź nowy interfejs
Speaker
i spuść do interfejsu. Jest to bardziej elastyczne niż w zależności od konkretnego rodzaju betonu. Ma tę wadę, że trzeba ją zmodyfikować,Human
aby zaimplementowaćSpeaker
. To nie zadziała, jeśli nie możesz modyfikowaćHuman
Downcast to
Human
. Ma to tę wadę, że będziesz musiał zmodyfikować kod, ilekroć chcesz mówić innej podklasie. Idealnie chcesz rozszerzyć aplikacje, dodając kod bez ciągłego cofania się i zmieniania starego kodu.źródło