OOD: herencia de Java y acceso a métodos secundarios mediante conversión

8

Tengo varias clases Parenty Child1... Child9implementado en Java. Parentes una clase abstracta, que contiene todas las variables comunes de las clases secundarias (muchas, que es la razón principal por la que hice Parentuna clase abstracta y no una interfaz), algunos métodos abstractos y algunos implementados.

Algunas de las clases secundarias tienen métodos personalizados que son específicos para ellos. Muy a menudo me encuentro llamando a un método hijo usando downcasting:

Parent p = new Child1();
((Child1) p).child1SpecificMethod();

De alguna manera tengo la sensación de que esta es una mala práctica de OOD, pero no estoy muy seguro de si ese es realmente el caso, respectivamente, de cómo mejorar el diseño.

- Editar: lo que probablemente debería cambiar de todos modos es el hecho de que uso la clase Parent para organizar muchas (por ahora) variables comunes, haciéndolas (o un objeto contenedor) miembros de las clases concretas.

jpmath
fuente
66
Si sabe que el valor de p es de tipo Child1, ¿por qué no hacer p de tipo Child1, por lo que no es necesario lanzar?
Benni
2
Generalmente sí, esta es una mala práctica. Si te encuentras desanimado, es probable que hayas hecho algo mal. Sin embargo, es realmente difícil decir más sin más información sobre su situación.
Aviv Cohn
44
¿Por qué estás declarando pcomo un Parenten primer lugar? Cumplir con los tipos generales es una buena práctica para las API y los tipos de retorno, pero no dentro del mismo ámbito en el que usted asigna una clase concreta.
Kilian Foth
Parece que estás heredando calzado en tu caso de uso. Si los niños no pueden usar la herencia para aprovechar un padre común, tendrá que revisar el diseño de su clase
kolossus
2
posible duplicado de ¿Cómo evitar el downcasting?
mosquito

Respuestas:

11

Esto no es solo una mala práctica , es innecesariamente complicado.

¿Por qué usas la herencia en general?

Cuando usa la herencia, tiene un conjunto de comportamientos comunes, que desea poner a disposición de muchos operadores diferentes. Esto incluye la herencia de clases y también la herencia de la interfaz . El heredero , por así decirlo, es a menudo una especialización de la clase de la que hereda; lo cual es principalmente cierto para la herencia de clase.

Piense en un auto de clase y un porche de subclase (lo típico es una relación ). Tiene un comportamiento general como arrancar / parar el motor , la dirección , etc. Si trata a un Porsche como un automóvil, está obligado a este aspecto de su comportamiento. Si sabe que solo quiere un Porsche y solo lo trata como un Porsche , es redundante instanciar un Porsche como un automóvil y obtener el comportamiento de Porsche a través del casting .

El polimorfismo tiene sentido al revés:

Tiene un porsche y necesita tratarlo desde el aspecto de un automóvil; por ejemplo, conducir

Siempre y cuando su Porsche acepte girar a la izquierda , girar a la derecha , subir , bajar , etc., puede utilizar el polimorfismo / sustitución uno por otro.

Es mejor instanciar sus objetos en su forma especializada. Entonces podría aprovechar al máximo el polimorfismo y usarlo solo cuando lo necesite .

Dicho eso: Parent p = new Child1();no tiene sentido para mí.

Editar: implementaría porsche diferente (a través de la composición ), pero por el bien del ejemplo, es un automóvil.

Thomas Junk
fuente
4

Su sentimiento es correcto al considerarlo una mala práctica. Imagine su código de ejemplo un poco diferente:

Parent p = createObject();
((Child1) p).child1SpecificMethod();

¿Cómo sabes que el valor de p es realmente Child1? No lo hace y esto puede causar una ClassCastException en tiempo de ejecución. Si necesita llamar a child1SpecificMethod () en su código, debe asegurarse de que p sea del tipo Child1. Si eso no es posible porque el Objeto p se pasa a su código (por ejemplo, como un parámetro de método) como tipo Parent, podría considerar usar una variante del Visitor-Pattern y ejecutar child1SpecificMethod en el handle-Method de su objeto visitante, que maneja Niño1.

Benni
fuente
"debe asegurarse de que p sea del tipo Child1", eso no es posible porque el objeto p se pasa como parámetro del método. El patrón de visitante es una buena pista.
jpmath
4

Utilice la búsqueda de capacidades en su lugar. No dé acceso a las clases secundarias, considérelas implementaciones de la clase primaria

Luego defina interfaces especificando alguna capacidad , característica.

interface Child1Specific {
    void child1SpecificMethod();
}

Uso:

Parent parent = ...
Child1Specific specific = parent.lookup(Child1Specific.class);
if (specific1 != null) {
    specific1.child1SpecificMethod();
}

Este mecanismo de descubrimiento es muy flexible. Usar delegación en lugar de herencia puede ser bastante gratificante. Tenga en cuenta que ya no es necesario tener clases secundarias.

O en Java 8 (donde son posibles varias variaciones, y la interfaz también podría ser funcional):

Optional<Child1Specific> specific = parent.lookup(Child1Specific.class);
if (specific1.isPresent()) {
    specific1.get().child1SpecificMethod();
}

Haga en la clase Parent una búsqueda de alguna capacidad:

public class Parent {
    protected final Map<Class<?>, Object> capabilities = new HashMap<>();
    protected final <T> void registerCapability(Class<T> klass, T object);

    public <T> T lookup(Class<T> klass) {
        Object object = capabilities.get(klass);
        return object == null ? null : klass.cast(object);
    }

O en Java 8:

    public <T> Optional<T> lookup(Class<T> klass) {
        Object object = capabilities.get(klass);
        return Optional.ofNullable(klass.cast(object));
    }

La clase infantil:

class Child1 extend Parent implements Child1Specific {
    Child1() {
        registerCapability(Child1Specific.class, this);
    }
}

O más dinámico:

class Child1 extends Parent {
    private Child1Specific specific = new Child1Specific() {
        ... Parent.this ...
    };
    Child1() {
        registerCapability(Child1Specific.class, specific);
    }
}
Joop Eggen
fuente
-3

Agregue un método abstracto child1SpecificMethod () en la clase Parent (debe marcar la clase como abstract) y proporcione su implementación en la clase secundaria respectiva.

Niraj
fuente