Minha pergunta é sobre um caso especial da super classe Animal.
- Minha
Animal
latamoveForward()
eeat()
. Seal
se estendeAnimal
.Dog
se estendeAnimal
.- E há uma criatura especial que também se estende
Animal
chamadaHuman
. Human
implementa também um métodospeak()
(não implementado porAnimal
).
Em uma implementação de um método abstrato que aceite, Animal
eu gostaria de usar o speak()
método. Isso parece impossível sem fazer um downcast. Jeremy Miller escreveu em seu artigo que um downcast cheira.
Qual seria uma solução para evitar o downcasting nessa situação?
java
object-oriented
type-casting
Bart Weber
fonte
fonte
Respostas:
Se você possui um método que precisa saber se a classe específica é do tipo
Human
para fazer algo, está quebrando alguns princípios do SOLID , particularmente:Na minha opinião, se seu método espera um tipo de classe específico, para chamá-lo de método específico, altere esse método para aceitar apenas essa classe, e não sua interface.
Algo assim :
e não assim:
fonte
Animal
chamadocanSpeak
e cada implementação concreta deve definir se pode ou não "falar".public void makeAnimalDoDailyThing(Animal animal) {animal.moveForward(); animal.eat()}
epublic void makeAnimalDoDailyThing(Human human) {human.moveForward(); human.eat(); human.speak();}
O problema não é que você está fazendo downcast - é para onde está fazendo downcast
Human
. Em vez disso, crie uma interface:Dessa forma, a condição não é que o animal seja
Human
- a condição é que ele possa falar. Isso significa quemysteriousMethod
pode trabalhar com outras subclasses não humanasAnimal
, desde que sejam implementadasCanSpeak
.fonte
Animal
e que todos os usuários desse método retenham o objeto que desejam enviar a ele por meio de umaCanSpeak
referência de tipo (ou mesmo deHuman
referência de tipo). Se fosse esse o caso, esse método poderia ter sido usadoHuman
em primeiro lugar e não precisaríamos apresentá-loCanSpeak
.CanSpeak
em primeiro lugar não é o fato de termos algo que o implementa (Human
), mas de algo que o utiliza (o método). O objetivo deCanSpeak
é dissociar esse método da classe concretaHuman
. Se não tivéssemos métodos que tratassem as coisas de maneiraCanSpeak
diferente, não faria sentido distinguir as coisas assimCanSpeak
. Nós não criamos uma interface só porque temos um método ...Você pode adicionar o Communicate to Animal. Cachorro late, Humano fala, Selo .. uhh ... Eu não sei o que selo faz.
Mas parece que seu método foi desenvolvido para if (Animal is Human) Speak ();
A pergunta que você pode querer fazer é: qual é a alternativa? É difícil dar uma sugestão, pois não sei exatamente o que você deseja alcançar. Existem situações teóricas em que downcasting / upcasting é a melhor abordagem.
fonte
Nesse caso, a implementação padrão de
speak()
naAbstractAnimal
classe seria:Nesse ponto, você tem uma implementação padrão na classe Abstract - e ela se comporta corretamente.
Sim, isso significa que você tem try-catchs espalhados pelo código para lidar com todos
speak
, mas a alternativa éif(thingy is Human)
envolver todas as conversas.A vantagem da exceção é que, se você tiver outro tipo de coisa em algum momento que fala (um papagaio), não precisará reimplementar todos os seus testes.
fonte
canSpeak()
método para lidar com isso melhor.Às vezes, o downcasting é necessário e apropriado. Em particular, geralmente é apropriado nos casos em que se possui objetos que podem ou não ter alguma habilidade, e se deseja usá-la quando existe enquanto manipula objetos sem essa habilidade de alguma maneira padrão. Como um exemplo simples, suponha que
String
se pergunte a se é igual a algum outro objeto arbitrário. Para que umString
seja igual ao outroString
, ele deve examinar o comprimento e a matriz de caracteres de apoio da outra sequência. Se aString
é perguntado se é igual aDog
, no entanto, ele não pode acessar o comprimento deDog
, mas não deveria; em vez disso, se o objeto ao qualString
se supostamente se comparar não for umString
, a comparação deve usar um comportamento padrão (relatando que o outro objeto não é igual).O momento em que o downcasting deve ser considerado o mais duvidoso é quando o objeto que está sendo transmitido é "conhecido" como sendo do tipo apropriado. Em geral, se um objeto é conhecido como a
Cat
, deve-se usar uma variável do tipoCat
, em vez de uma variável do tipoAnimal
, para se referir a ele. Há momentos em que isso nem sempre funciona, no entanto. Por exemplo, umaZoo
coleção pode conter pares de objetos em slots de matriz pares / ímpares, com a expectativa de que os objetos em cada par possam agir um contra o outro, mesmo que não possam agir sobre os objetos em outros pares. Nesse caso, os objetos em cada par ainda teriam que aceitar um tipo de parâmetro não específico, de forma que eles pudessem, sintaticamente , passar os objetos de qualquer outro par. Assim, mesmo seCat
'splayWith(Animal other)
O método só funcionaria quandoother
fosse aCat
,Zoo
seria necessário transmitir a ele um elemento de umAnimal[]
, portanto, seu tipo de parâmetro teria que ser emAnimal
vez deCat
.Nos casos em que o downcasting é legitimamente inevitável, deve-se usá-lo sem receio. A questão-chave é determinar quando é possível evitar sensivelmente o downcasting e evitá-lo quando possível.
fonte
Object.equalToString(String string)
. Então você temboolean String.equal(Object object) { return object.equalStoString(this); }
Então, não é necessário fazer downcast: você pode usar o envio dinâmico.Object
tem nenhumequalStoString
método virtual e admito que não sei como o exemplo citado funcionaria em Java, mas em C #, o despacho dinâmico (diferente do despacho virtual) significaria que o compilador tem essencialmente para fazer a pesquisa de nome com base no Reflection na primeira vez em que um método é usado em uma classe, que é diferente do envio virtual (que simplesmente faz uma chamada por um slot na tabela de métodos virtual, necessário para conter um endereço de método válido).Você tem poucas escolhas:
Use reflexão para chamar,
speak
se existir. Vantagem: sem dependênciaHuman
. Desvantagem: agora existe uma dependência oculta do nome "speak".Introduzir uma nova interface
Speaker
e fazer downcast na interface. Isso é mais flexível do que depende de um tipo específico de concreto. Tem a desvantagem que você precisa modificarHuman
para implementarSpeaker
. Isso não funcionará se você não puder modificarHuman
Downcast para
Human
. Isso tem a desvantagem de que você precisará modificar o código sempre que desejar que outra subclasse fale. Idealmente, você deseja estender aplicativos adicionando código sem voltar e alterar repetidamente o código antigo.fonte