Мой вопрос касается особого случая суперкласса Animal.
- Моя
Animal
можетmoveForward()
иeat()
. Seal
расширяетсяAnimal
.Dog
расширяетсяAnimal
.- И есть специальное существо, которое также расширяется,
Animal
называетсяHuman
. Human
реализует также методspeak()
(не реализованAnimal
).
В реализации абстрактного метода, который принимает, Animal
я хотел бы использовать speak()
метод. Это кажется невозможным без ударов. Джереми Миллер написал в своей статье, что пахнет удрученным.
Каково было бы решение избежать уныния в этой ситуации?
java
object-oriented
type-casting
Барт Вебер
источник
источник
Ответы:
Если у вас есть метод, который должен знать, имеет ли конкретный класс тип
Human
, чтобы что-то сделать, то вы нарушаете некоторые принципы SOLID , в частности:По моему мнению, если ваш метод ожидает определенного типа класса, чтобы вызвать его конкретный метод, измените этот метод так, чтобы он принимал только этот класс, а не его интерфейс.
Что-то вроде этого :
и не так
источник
Animal
вызываемого,canSpeak
и каждая конкретная реализация должна определить, может ли он «говорить».public void makeAnimalDoDailyThing(Animal animal) {animal.moveForward(); animal.eat()}
иpublic void makeAnimalDoDailyThing(Human human) {human.moveForward(); human.eat(); human.speak();}
Проблема не в том, что вы унижены, а в том, что вы унижаетесь
Human
. Вместо этого создайте интерфейс:Таким образом, условие не в том, что животное есть, а
Human
в том, что оно может говорить. Это означает, что ониmysteriousMethod
могут работать с другими, не относящимися к человеку подклассами доAnimal
тех пор, пока они реализуютсяCanSpeak
.источник
Animal
, и что все пользователи этого метода будут содержать объект, который они хотят отправить ему черезCanSpeak
ссылку на тип (или дажеHuman
ссылку на тип). Если бы это было так, этот метод мог бы использоватьсяHuman
в первую очередь, и нам не нужно было бы его вводитьCanSpeak
.CanSpeak
впервые представили, заключается не в том, что у нас есть что-то, что реализует это (Human
), а в том, что у нас есть то, что использует это (метод). ТочкаCanSpeak
является разъединить этот метод из класса бетонаHuman
. Если бы у нас не было методов, позволяющих обращаться с вещами по-CanSpeak
другому, не было бы смысла выделять такие вещиCanSpeak
. Мы не создаем интерфейс только потому, что у нас есть метод ...Вы можете добавить Общение с животными. Собака лает, Человек говорит, Печать ... э-э-э ... Я не знаю, что делает печать.
Но, похоже, ваш метод предназначен для if (Animal is Human) Speak ();
Вопрос, который вы, возможно, захотите задать, - какая альтернатива? Трудно дать предложение, так как я не знаю точно, чего вы хотите достичь. Существуют теоретические ситуации, в которых даункастинг / апкастинг является лучшим подходом.
источник
В этом случае реализация по умолчанию
speak()
вAbstractAnimal
классе будет:В этот момент у вас есть реализация по умолчанию в классе Abstract - и она ведет себя правильно.
Да, это означает, что у вас есть попытки поймать разбросанные по коду для обработки каждого
speak
, но альтернативой этому являетсяif(thingy is Human)
вместо этого упаковка всех спиц.Преимущество исключения состоит в том, что если в какой-то момент у вас есть что-то, что говорит (попугай), вам не нужно переопределять все свои тесты.
источник
canSpeak()
метод, чтобы справиться с этим лучше.Даункинг иногда необходим и уместен. В частности, это часто уместно в тех случаях, когда у кого-то есть объекты, которые могут иметь или не иметь какую-то способность, и кто-то хочет использовать эту способность, когда она существует, при обработке объектов без этой способности каким-либо способом по умолчанию. В качестве простого примера, предположим, что a
String
спрашивают, равен ли он некоторому другому произвольному объекту. Чтобы одноString
совпадало с другимString
, оно должно проверять длину и массив вспомогательных символов другой строки. Если aString
спрашивают, равно ли оно aDog
, он не может получить доступ к длинеDog
, но он не должен этого делать; вместо этого, если объект, с которымString
должен сравнивать a, не являетсяString
сравнение должно использовать поведение по умолчанию (сообщая, что другой объект не равен).Время, когда удушение следует считать наиболее сомнительным, это когда «известный» объект, о котором известно, что он имеет надлежащий тип. В общем, если известно, что объект является a
Cat
, следует использовать переменную типаCat
, а не переменную типаAnimal
, чтобы ссылаться на него. Однако бывают случаи, когда это не всегда работает. Например,Zoo
коллекция может содержать пары объектов в четных / нечетных слотах массива, ожидая, что объекты в каждой паре смогут воздействовать друг на друга, даже если они не могут воздействовать на объекты в других парах. В таком случае объекты в каждой паре все равно должны будут принимать неспецифический тип параметра, чтобы они могли синтаксически передавать объекты из любой другой пары. Таким образом, даже еслиCat
этоplayWith(Animal other)
Метод будет работать только в том случае, если онother
былCat
, аZoo
должен иметь возможность передавать ему элементAnimal[]
, поэтому его тип параметра должен быть,Animal
а неCat
.В тех случаях, когда снижение рейтинга на законных основаниях неизбежно, его следует использовать без каких-либо сомнений. Ключевой вопрос состоит в том, чтобы определить, когда можно разумно избегать удушения, и избегать его, когда это разумно возможно.
источник
Object.equalToString(String string)
. Тогда у вас естьboolean String.equal(Object object) { return object.equalStoString(this); }
Итак, нет необходимости в понижении: вы можете использовать динамическую диспетчеризацию.Object
есть какой-либоequalStoString
виртуальный метод, и я признаю, что я не знаю, как приведенный пример будет работать даже в Java, но в C # динамическая диспетчеризация (в отличие от виртуальной диспетчеризации) будет означать, что компилятор по существу имеет выполнить поиск имени на основе отражений при первом использовании метода в классе, который отличается от виртуальной диспетчеризации (которая просто выполняет вызов через слот в таблице виртуальных методов, которая должна содержать действительный адрес метода).У вас есть несколько вариантов:
Используйте отражение, чтобы позвонить,
speak
если он существует. Преимущество: нет зависимости отHuman
. Недостаток: теперь есть скрытая зависимость от имени «говорить».Ввести новый интерфейс
Speaker
и снизить интерфейс. Это более гибко, чем в зависимости от конкретного типа бетона. Недостатком является то, что вы должны изменить егоHuman
для реализацииSpeaker
. Это не сработает, если вы не можете изменитьHuman
Унизить до
Human
. Это имеет тот недостаток, что вам придется изменять код всякий раз, когда вы хотите, чтобы другой подкласс говорил. В идеале вы хотите расширить приложения, добавляя код, не возвращаясь и не меняя старый код.источник