Mi pregunta es sobre un caso especial de la súper clase Animal.
- Mi
Animal
latamoveForward()
yeat()
. Seal
se extiendeAnimal
.Dog
se extiendeAnimal
.- Y hay una criatura especial que también se extiende
Animal
llamadaHuman
. Human
implementa también un métodospeak()
(no implementado porAnimal
).
En una implementación de un método abstracto que acepta, Animal
me gustaría usar el speak()
método. Eso no parece posible sin hacer un downcast. Jeremy Miller escribió en su artículo que huele mal.
¿Cuál sería una solución para evitar abatir en esta situación?
java
object-oriented
type-casting
Bart Weber
fuente
fuente
Respuestas:
Si tiene un método que necesita saber si la clase específica es de tipo
Human
para hacer algo, entonces está rompiendo algunos principios SÓLIDOS , particularmente:En mi opinión, si su método espera un tipo de clase particular, para llamar a su método particular, entonces cambie ese método para aceptar solo esa clase, y no su interfaz.
Algo como esto :
y no asi:
fuente
Animal
llamadocanSpeak
y cada implementación concreta debe definir si puede o no "hablar".public void makeAnimalDoDailyThing(Animal animal) {animal.moveForward(); animal.eat()}
ypublic void makeAnimalDoDailyThing(Human human) {human.moveForward(); human.eat(); human.speak();}
El problema no es que estés abatiendo, sino que estás abatiendo
Human
. En cambio, cree una interfaz:De esta manera, la condición no es que el animal lo sea
Human
, sino que puede hablar. Esto significa quemysteriousMethod
puede funcionar con otras subclases no humanasAnimal
mientras se implementenCanSpeak
.fuente
Animal
, y que todos los usuarios de ese método mantendrán el objeto que desean enviarle a través de unaCanSpeak
referencia de tipo (o inclusoHuman
referencia de tipo). Si ese fuera el caso, ese método podría haberse utilizadoHuman
en primer lugar y no necesitaríamos presentarloCanSpeak
.CanSpeak
en primer lugar no es que tengamos algo que lo implemente (Human
) sino que tengamos algo que lo use (el método). El puntoCanSpeak
es desacoplar ese método de la clase concretaHuman
. Si no tuviéramos métodos para tratar las cosas de maneraCanSpeak
diferente, no tendría sentido distinguir las cosasCanSpeak
. No creamos una interfaz solo porque tenemos un método ...Puede agregar Comunicar a Animal. El perro ladra, el humano habla, Seal ... uhh ... No sé qué hace Seal.
Pero parece que su método está diseñado para si (Animal is Human) Speak ();
La pregunta que puede hacer es, ¿cuál es la alternativa? Es difícil dar una sugerencia ya que no sé exactamente lo que quieres lograr. Hay situaciones teóricas en las que el mejor enfoque es el downcasting / upcasting.
fuente
En este caso, la implementación predeterminada de
speak()
en laAbstractAnimal
clase sería:En ese momento, tiene una implementación predeterminada en la clase Resumen, y se comporta correctamente.
Sí, esto significa que tiene capturas de prueba dispersas a través del código para manejar cada una
speak
, pero la alternativa a esto esif(thingy is Human)
envolver todos los discursos en su lugar.La ventaja de la excepción es que si tiene otro tipo de cosas en algún momento que habla (un loro), no necesitará volver a implementar todas sus pruebas.
fuente
canSpeak()
método para manejar esto mejor.La abatimiento es a veces necesaria y apropiada. En particular, a menudo es apropiado en los casos en que uno tiene objetos que pueden o no tener alguna habilidad, y uno desea usar esa habilidad cuando existe mientras maneja objetos sin esa habilidad de alguna manera predeterminada. Como ejemplo simple, supongamos que
String
se le pregunta a a si es igual a algún otro objeto arbitrario. Para que uno seaString
igual a otroString
, debe examinar la longitud y la matriz de caracteres de respaldo de la otra cadena. Si unaString
se le pregunta si es igual a unDog
embargo, no se puede acceder a la longitud de laDog
, pero no tiene por qué; en cambio, si el objeto con el queString
se supone que se compara no es unString
, la comparación debe usar un comportamiento predeterminado (informar que el otro objeto no es igual).El momento en que se debe considerar que la abatición es más dudosa es cuando se "sabe" que el objeto que se está lanzando es del tipo adecuado. En general, si se sabe que un objeto es a
Cat
, se debe usar una variable de tipoCat
, en lugar de una variable de tipoAnimal
, para referirse a él. Sin embargo, hay momentos en que esto no siempre funciona. Por ejemplo, unaZoo
colección podría contener pares de objetos en ranuras de matriz pares / impares, con la expectativa de que los objetos de cada par puedan actuar unos sobre otros, incluso si no pueden actuar sobre los objetos de otros pares. En tal caso, los objetos en cada par aún tendrían que aceptar un tipo de parámetro no específico de modo que, sintácticamente , pudieran pasar los objetos desde cualquier otro par. Por lo tanto, incluso siCat
'splayWith(Animal other)
El método solo funcionaría cuandoother
fuera unCat
, elZoo
tendría que poder pasarle un elemento de unAnimal[]
, por lo que su tipo de parámetro tendría que ser enAnimal
lugar deCat
.En los casos en que la derrota sea legítimamente inevitable, uno debería usarla sin reparos. La pregunta clave es determinar cuándo se puede evitar sensacionalmente el rechazo y evitarlo cuando sea razonablemente posible.
fuente
Object.equalToString(String string)
. Entonces tieneboolean String.equal(Object object) { return object.equalStoString(this); }
So, no necesita downcast: puede usar el despacho dinámico.Object
tenga ningúnequalStoString
método virtual, y admito que no sé cómo funcionaría el ejemplo citado en Java, pero en C #, el despacho dinámico (a diferencia del despacho virtual) significaría que el compilador esencialmente tiene hacer una búsqueda de nombres basada en Reflection la primera vez que se usa un método en una clase, que es diferente del despacho virtual (que simplemente realiza una llamada a través de un espacio en la tabla de métodos virtuales que se requiere para contener una dirección de método válida).Tienes algunas opciones:
Use la reflexión para llamar
speak
si existe. Ventaja: no depende deHuman
. Desventaja: ahora hay una dependencia oculta en el nombre "hablar".Introducir una nueva interfaz
Speaker
y bajar a la interfaz. Esto es más flexible que dependiendo de un tipo de concreto específico. Tiene la desventaja de que tiene que modificarHuman
para implementarSpeaker
. Esto no funcionará si no puedes modificarHuman
Abatido a
Human
. Esto tiene la desventaja de que tendrá que modificar el código cada vez que desee que hable otra subclase. Idealmente, desea extender las aplicaciones agregando código sin tener que retroceder repetidamente y cambiar el código antiguo.fuente