Mi pregunta es sobre un caso especial de la súper clase Animal.
- Mi
AnimallatamoveForward()yeat(). Sealse extiendeAnimal.Dogse extiendeAnimal.- Y hay una criatura especial que también se extiende
AnimalllamadaHuman. Humanimplementa también un métodospeak()(no implementado porAnimal).
En una implementación de un método abstracto que acepta, Animalme 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
Humanpara 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
AnimalllamadocanSpeaky 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 quemysteriousMethodpuede funcionar con otras subclases no humanasAnimalmientras se implementenCanSpeak.fuente
Animal, y que todos los usuarios de ese método mantendrán el objeto que desean enviarle a través de unaCanSpeakreferencia de tipo (o inclusoHumanreferencia de tipo). Si ese fuera el caso, ese método podría haberse utilizadoHumanen primer lugar y no necesitaríamos presentarloCanSpeak.CanSpeaken primer lugar no es que tengamos algo que lo implemente (Human) sino que tengamos algo que lo use (el método). El puntoCanSpeakes desacoplar ese método de la clase concretaHuman. Si no tuviéramos métodos para tratar las cosas de maneraCanSpeakdiferente, 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 laAbstractAnimalclase 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
Stringse le pregunta a a si es igual a algún otro objeto arbitrario. Para que uno seaStringigual a otroString, debe examinar la longitud y la matriz de caracteres de respaldo de la otra cadena. Si unaStringse le pregunta si es igual a unDogembargo, no se puede acceder a la longitud de laDog, pero no tiene por qué; en cambio, si el objeto con el queStringse 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, unaZoocolecció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 cuandootherfuera unCat, elZootendría que poder pasarle un elemento de unAnimal[], por lo que su tipo de parámetro tendría que ser enAnimallugar 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.Objecttenga ningúnequalStoStringmé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
speaksi existe. Ventaja: no depende deHuman. Desventaja: ahora hay una dependencia oculta en el nombre "hablar".Introducir una nueva interfaz
Speakery 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 modificarHumanpara implementarSpeaker. Esto no funcionará si no puedes modificarHumanAbatido 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