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

8

Ich habe mehrere Klassen Parentund Child1... Child9in Java implementiert. Parentist eine abstrakte Klasse, die alle allgemeinen Variablen der untergeordneten Klassen enthält (viel, was der Hauptgrund ist, warum ich Parenteine abstrakte Klasse und keine Schnittstelle erstellt 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.

- Bearbeiten - 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.

jpmath
quelle
6
Wenn Sie wissen, dass der Wert von p vom Typ Child1 ist, warum nicht p vom Typ Child1 machen, sodass keine Umwandlung erforderlich ist?
Benni
2
Im Allgemeinen ja, das ist eine schlechte Praxis. Wenn Sie häufig Dinge ablehnen, haben Sie wahrscheinlich etwas falsch gemacht. Es ist jedoch sehr schwer, mehr zu sagen, ohne mehr Informationen über Ihre Situation zu haben.
Aviv Cohn
4
Warum erklärst du dich überhaupt pals Parent? Das Festhalten an allgemeinen Typen ist eine gute Vorgehensweise für APIs und Rückgabetypen, jedoch nicht in demselben Bereich, in dem Sie selbst eine konkrete Klasse zuweisen.
Kilian Foth
Sieht so aus, als würden Sie die Vererbung in Ihren Anwendungsfall einbinden. Wenn die Kinder die Vererbung nicht nutzen können, um einen gemeinsamen Elternteil auszunutzen, müssen Sie Ihr Klassendesign überprüfen
Koloss
2
mögliches Duplikat von Wie vermeide ich Downcasting?
Mücke

Antworten:

11

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

Warum verwenden Sie die 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 umfasst sowohl die Klassenvererbung als auch die Schnittstellenvererbung . Der Erbe ist sozusagen oft eine Spezialisierung der Klasse, von der er erbt; Dies gilt hauptsächlich für die Klassenvererbung.

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

Polymorphismus macht umgekehrt Sinn:

Sie haben einen Porsche und müssen ihn unter dem Gesichtspunkt eines Autos behandeln. zB fahren

Solange Ihr Porsche das Lenken nach links , Lenken nach rechts , Verschieben nach oben , Verschieben nach unten usw. akzeptiert , können Sie Polymorphismus / Substitution für einander verwenden.

Es ist besser, Ihre Objekte in ihrer speziellen Form zu instanziieren. Dann können Sie den Polymorphismus optimal nutzen und ihn nur dann verwenden, wenn Sie ihn benötigen .

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

Edit: Ich würde Porsche anders (über Komposition ) implementieren , aber für das Beispiel ist es ein Auto.

Thomas Junk
quelle
4

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 Besuchermusters verwenden und child1SpecificMethod in der Handle-Methode Ihres Besucherobjekts ausführen, die behandelt wird Kind1.

Benni
quelle
"Sie müssen sicherstellen, dass p vom Typ Child1 ist" - das ist in der Tat nicht möglich, da Objekt p als Methodenparameter übergeben wird. Das Besuchermuster ist ein guter Hinweis.
Jpmath
4

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 bestimmte Funktionen und Merkmale 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);
    }
}
Joop Eggen
quelle
-3

Fügen Sie eine abstrakte Methode child1SpecificMethod () in die übergeordnete Klasse ein (Sie müssen die Klasse als abstrakt markieren) und stellen Sie ihre Implementierung in der jeweiligen untergeordneten Klasse bereit.

Niraj
quelle