it-swarm.com.de

OOD: Java Vererbung und Zugriff auf untergeordnete Methoden über Casting

Ich habe mehrere Klassen Parent und Child1 ... Child9 in Java implementiert. Parent ist eine abstrakte Klasse, die alle allgemeinen Variablen der untergeordneten Klassen enthält (viel, was der Hauptgrund ist, warum ich Parent eine abstrakte Klasse und keine Schnittstelle gemacht habe), einige abstrakte und einige implementierte Methoden.

Einige der untergeordneten Klassen verfügen über benutzerdefinierte Methoden, die für sie spezifisch sind. So oft rufe ich eine untergeordnete Methode mit Downcasting auf:

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

Irgendwie habe ich das Gefühl, dass dies eine schlechte OOD-Praxis ist, aber ich bin mir nicht ganz sicher, ob dies wirklich der Fall ist bzw. wie das Design verbessert werden kann.

- Edit - Was ich wahrscheinlich sowieso ändern sollte, ist die Tatsache, dass ich die Parent-Klasse verwende, um viele (vorerst) allgemeine Variablen zu organisieren und sie (oder ein Containerobjekt) zu Mitgliedern der konkreten Klassen zu machen.

8
jpmath

Dies ist nicht nur schlechte Praxis, dies ist unnötig kompliziert.

Warum verwenden Sie Vererbung im Allgemeinen?

Wenn Sie die Vererbung verwenden, haben Sie ein gemeinsames Verhalten, das Sie für viele verschiedene Netzbetreiber verfügbar machen möchten. Dies beinhaltet Klassenvererbung sowie Schnittstellenvererbung. Der Erbe ist sozusagen oft ein Spezialisierung der Klasse, von der er erbt; Dies gilt hauptsächlich für die Klassenvererbung.

Denken Sie an eine Klasse Auto und eine Unterklasse Porsche (die typische ist eine - Beziehung). Sie haben ein allgemeines Verhalten wie Starten/Stoppen des Motors, Lenken und so weiter. Wenn Sie ein porsche wie ein Auto behandeln, sind Sie an diesen Aspekt seines Verhaltens gebunden. Wenn Sie wissen, dass Sie nur ein Porsche wollen und nur damit umgehen als Porsche, ist es überflüssig, ein Porsche als - zu instanziieren Auto und erhalten Porsche-Verhalten über Casting.

Polymorphismus macht umgekehrt Sinn:

Sie haben einen Porsche und müssen ihn unter dem Gesichtspunkt eines Autos behandeln; z. B. Fahren

Solange dein porsche akzeptiert nach links lenken, nach rechts lenken, nach oben schalten, nach unten schalten usw. Sie können Polymorphismus/Substitution untereinander verwenden.

Es ist besser, Ihre Objekte in ihrer speziellen Form zu instanziieren. Dann könnten Sie das Beste aus Polymorphismus machen und es nur verwenden, wenn Sie brauchen es.

Das heißt: Parent p = new Child1(); macht für mich keinen Sinn.

Edit: Ich würde porsche different (via Komposition) implementieren, aber für das Beispiel ist es ist ein Auto.

11
Thomas Junk

Ihr Gefühl ist richtig, es als schlechte Praxis zu betrachten. Stellen Sie sich Ihren Beispielcode etwas anders vor:

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

Woher weißt du, dass der Wert von p wirklich Child1 ist? Sie tun dies nicht und dies kann zur Laufzeit eine ClassCastException verursachen. Wenn Sie child1SpecificMethod () in Ihrem Code aufrufen müssen, müssen Sie sicherstellen, dass p vom Typ Child1 ist. Wenn dies nicht möglich ist, weil Objekt p als Typ Parent an Ihren Code übergeben wird (z. B. als Methodenparameter), können Sie eine Variante des Visitor-Pattern verwenden und child1SpecificMethod in der handle-Methode ausführen Ihres Besucherobjekts, das Child1 behandelt.

4
Benni

Verwenden Sie stattdessen die Suche nach Funktionen. Geben Sie keinen Zugriff auf die untergeordneten Klassen, betrachten Sie sie als Implementierungen der übergeordneten Klasse.

Definieren Sie dann Schnittstellen, die eine Funktion Fähigkeit, angeben.

interface Child1Specific {
    void child1SpecificMethod();
}

Verwendungszweck:

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

Dieser Erkennungsmechanismus ist sehr flexibel. Die Verwendung von Delegierung anstelle von Vererbung kann sehr lohnend sein. Beachten Sie, dass untergeordnete Klassen nicht mehr benötigt werden.

Oder in Java 8 (wo mehrere Variationen möglich sind und die Schnittstelle auch funktionsfähig sein könnte):

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

Machen Sie in der Parent-Klasse eine Suche nach Funktionen:

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

Oder in Java 8:

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

Die Kinderklasse:

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

Oder dynamischer:

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