web-dev-qa-db-fra.com

OOD: Java héritage et accès aux méthodes enfants via le cast

J'ai plusieurs classes Parent et Child1 ... Child9 implémenté en Java. Parent est une classe abstraite, contenant toutes les variables communes des classes enfants (beaucoup, ce qui est la principale raison pour laquelle j'ai fait de Parent une classe abstraite et non une interface), certaines abstraites et quelques méthodes implémentées.

Certaines classes enfants ont des méthodes personnalisées qui leur sont spécifiques. Je me retrouve donc souvent à appeler une méthode enfant en utilisant le downcasting:

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

D'une certaine manière, j'ai le sentiment que c'est une mauvaise pratique OOD, mais je ne suis pas sûr que ce soit vraiment le cas, respectivement, comment améliorer la conception.

- Edit - Ce que je devrais probablement changer de toute façon, c'est le fait que j'utilise la classe Parent pour organiser de nombreuses variables communes (pour l'instant), en les faisant (ou un objet conteneur) membres des classes concrètes.

8
jpmath

Ce n'est pas seulement mauvaise pratique, c'est compliqué inutile.

Pourquoi utilisez-vous héritage en général?

Lorsque vous utilisez l'héritage, vous avez un ensemble de comportements communs que vous souhaitez rendre disponible pour de nombreux opérateurs différents. Cela inclut héritage de classe ainsi que héritage d'interface. Le héritier, pour ainsi dire, est souvent un spécialisation de la classe dont il hérite; ce qui est principalement vrai pour l'héritage de classe.

Pensez à une classe voiture et à une sous-classe porsche (la relation typique est une -). Vous avez un comportement général comme démarrage/arrêt du moteur, direction et ainsi de suite. Si vous traitez un porsche comme une voiture, vous êtes lié à cet aspect de son comportement. Si vous savez que vous ne voulez qu'un porsche et que vous le traitez seulement en tant que porsche, il est redondant d'instancier un porsche en tant que - voiture et obtenez comportement Porsche via casting.

Le polymorphisme prend tout son sens:

Vous avez une Porsche et devez la traiter sous l'aspect d'une voiture, par exemple en conduisant

Tant que votre porsche accepte tourner à gauche, tourner à droite, monter, descendre, etc. vous pourriez utiliser le polymorphisme/substitution l'un pour l'autre.

Il est préférable d'instancier vos objets sous leur forme spécialisée. Ensuite, vous pouvez tirer le meilleur parti de polymorphisme et l'utiliser uniquement lorsque vous besoin.

Cela dit: Parent p = new Child1(); n'a aucun sens pour moi.

Edit: je mettrais en œuvre porsche différent (via composition), mais pour les besoins de l'exemple, il est une voiture.

11
Thomas Junk

Votre sentiment est juste de le considérer comme une mauvaise pratique. Imaginez votre exemple de code un peu différent:

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

Comment savez-vous que la valeur de p est vraiment Child1? Ce n'est pas le cas et cela peut provoquer une exception ClassCastException lors de l'exécution. Si vous devez appeler child1SpecificMethod () dans votre code, vous devez vous assurer que p est de type Child1. Si cela n'est pas possible car l'objet p est passé à votre code (par exemple en tant que paramètre de méthode) en tant que type Parent, vous pouvez envisager d'utiliser une variante de Visitor-Pattern et d'exécuter child1SpecificMethod dans la méthode de gestion. de votre objet visiteur, qui gère Child1.

4
Benni

Utilisez plutôt la recherche de capacités. Ne donnez aucun accès aux classes enfants, considérez-les comme des implémentations de la classe Parent.

Définissez ensuite des interfaces spécifiant une fonction capacité.

interface Child1Specific {
    void child1SpecificMethod();
}

Usage:

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

Ce mécanisme de découverte est très flexible. L'utilisation de la délégation au lieu de l'héritage peut être très enrichissante. Notez que la présence de classes enfants n'est plus nécessaire.

Ou en Java 8 (où plusieurs variantes sont possibles, et l'interface pourrait aussi être fonctionnelle):

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

Faites dans la classe Parent une recherche de capacité:

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);
    }

Ou en Java 8:

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

La classe enfant:

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

Ou plus dynamique:

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