Wenn Sie Vererbung verwenden, um Code wiederzuverwenden, finden Sie es zu schwierig, die Vorteile der Wiederverwendung zu verschlingen?


8

Ich habe ungefähr 8 Jahre lang codiert, aber ich finde immer noch, dass die Vererbung zu flexibel ist und Sie manchmal völlig verwirrt mit dem Code ist, den Sie geschrieben haben. Ein einfachstes Beispiel wäre:

abstract class AClass {
    protected void method1() {
        if(check()) {
            do1();
        } else {
            do2();
        }
    }
    protected abstract void do1();
    protected abstract void do2();
}

Die Absicht der Klasse ist, dass die Leute do1 () und do2 () implementieren können, so dass eine weitere Logik ausgeführt werden kann. Manchmal entscheiden sich die Leute jedoch, method1 () zu überladen, und dann werden die Dinge sofort kompliziert.

Ich finde nur im Strategiemuster, dass Code durch Vererbung gut wiederverwendet wird. In den meisten Fällen kennt der Designer der Basisklasse seine Unterklassen sehr gut, und diese Vererbung ist völlig optional.

Ich habe eine Klasse, die von 4 Klassen geerbt wurde - einen IoHandler, und es gibt Unterklassen für Server-, Client-, Edgeserver- und Ursprungsserver, und sie macht mich verrückt. Ich war immer im Code-Refactoring, ich kam immer mit Ideen heraus, von denen ich denke, dass sie funktionieren würden, und wurde dann nicht bewiesen. Es wird gesagt, dass das menschliche Gehirn nur 7 Informationen gleichzeitig speichern kann. Trage ich zu viele?


1
Sie wissen, dass Sie eine Überlastung bestimmter Methoden verhindern können? Markieren Sie es als final[wenn Sie Java verwenden]. Dies macht es effektiv nicht virtuell und es kann nicht überladen werden. Perfekt für den Einsatz unter diesen Umständen [Vorlagenmuster]
Farrell

@ Farrell Tatsächlich markiere ich es als "intensiv geschützt, damit es überladen werden kann". Ich denke, wenn ich das mache, verwende ich einige Ideen von MFC, die schlecht entworfen wurden.
Taktoth

2
Das Überladen von Methoden hat nichts mit Vererbung zu tun. Es ist wirklich seltsam, dass mehrere Kommentare und sogar die akzeptierte Antwort es wörtlich wiederholen. Sicherlich reden Sie über das Überschreiben ?
Aaronaught

@ Aronaught Nun, Ihre Korrektur ist wirklich nützlich :(
Tactoth

Antworten:


3

Sobald Sie solche Muster wiederholen, scheinen sie nicht mehr so ​​schwierig zu sein.

Außerdem müssen Sie die Basisklassen kennen, bevor Sie sie erben. Und die Vererbung ist nicht optional, solange allgemeiner Code wiederverwendet wird. Nehmen Sie ein anderes Muster wie abstraktes Factory / Composite - diese Vererbung erfüllt tatsächlich einen Zweck.

Faustregel: Verwenden Sie die Vererbung nicht nur, um eine Reihe von Klassen zusammenzuhalten. Verwenden Sie es, wo immer es Sinn macht. In dem erfundenen Beispiel muss der Benutzer der untergeordneten Klasse, die die Methode1 überlastet, einen schwerwiegenden Grund dafür haben. (OTOH, ich wollte auch einen Weg finden, um zu verhindern, dass bestimmte Funktionen alleine überlastet werden - um dies zu vermeiden). Und bevor Sie diese Kinderklasse verwenden, sollten Sie auch über diese Eigenart Bescheid wissen. Aber ein reines Strategiemuster reicht nicht aus - wenn ja, dokumentieren Sie es sehr gut.

Wenn Sie Ihr aktuelles Codebeispiel angeben, können die Leute hier es für Sie überprüfen.


Vielen Dank für die Antwort. Ich denke immer noch, dass Vererbung hauptsächlich zum Teilen von Logik verwendet wird. Ich habe immer das Prinzip angewendet, dass Codes, wenn sie geteilt werden können, geteilt werden sollten. Wenn Sie sich entscheiden, sie nicht zu teilen, wird dies klarer .
Taktoth

Das Codebeispiel ist zu groß, ich habe mehr als 30 Klassen, die aussortiert werden müssen.
Taktoth

18

Ja, es kann sehr schwierig sein ... und Sie sind in guter Gesellschaft.

Joshua Bloch in Effective Java erfasst einige der Vererbungsprobleme sehr gut und empfiehlt, die Komposition anstelle der Vererbung zu bevorzugen.

Vererbung ist eine leistungsstarke Methode zur Wiederverwendung von Code, aber nicht immer das beste Werkzeug für diesen Job. Bei unsachgemäßer Verwendung kommt es zu zerbrechlicher Software. Es ist sicher, die Vererbung in einem Paket zu verwenden, in dem die Unterklasse und die Oberklassenimplementierung von denselben Programmierern gesteuert werden. Es ist auch sicher, die Vererbung zu verwenden, wenn Klassen erweitert werden, die speziell für die Erweiterung entwickelt und dokumentiert wurden (Punkt 15). Das Erben von gewöhnlichen Betonklassen über Paketgrenzen hinweg ist jedoch gefährlich. Zur Erinnerung: In diesem Buch wird das Wort "Vererbung" als Implementierungsvererbung bezeichnet (wenn eine Klasse eine andere erweitert). Die in diesem Artikel beschriebenen Probleme gelten nicht für die Schnittstellenvererbung (wenn eine Klasse eine Schnittstelle implementiert oder wenn eine Schnittstelle eine andere erweitert).

Im Gegensatz zum Methodenaufruf unterbricht die Vererbung die Kapselung. Mit anderen Worten, eine Unterklasse hängt für ihre ordnungsgemäße Funktion von den Implementierungsdetails ihrer Oberklasse ab. Die Implementierung der Oberklasse kann sich von Release zu Release ändern. Wenn dies der Fall ist, kann die Unterklasse beschädigt werden, obwohl ihr Code nicht berührt wurde. Infolgedessen muss sich eine Unterklasse parallel zu ihrer Oberklasse entwickeln, es sei denn, die Autoren der Oberklasse haben sie speziell zum Zweck der Erweiterung entworfen und dokumentiert.


+1. Und beachten Sie, dass die Idee schon viel länger existiert und viel weiter verbreitet ist als nur Block und effektives Java . siehe hier .
Josh Kelley

1

Ich denke, Ihre Frage ist zu vage und allgemein. Was Sie beschreiben, klingt nach einem komplexen Problem mit oder ohne Vererbung. IMHO ist es also ganz normal, dass Sie damit zu kämpfen haben.

Natürlich ist in einigen Fällen die Vererbung nicht das beste Werkzeug für einen Job (obligatorischer Verweis auf "Komposition gegenüber Vererbung bevorzugen" ;-). Aber wenn ich es vorsichtig benutze, finde ich es nützlich. Anhand Ihrer Beschreibung ist schwer zu entscheiden, ob die Vererbung das richtige Werkzeug für Ihren Fall ist oder nicht.


Es ist eine vage Frage und die Absicht ist, einige Ideen zu sammeln, um Designentscheidungen zu erleichtern. Ich habe zwischen Komposition und Vererbung hin und her gewechselt.
Taktoth

1

Wenn Ihre Methoden zu komplex sind, werden Unterklassen zum Weinen gebracht

Lassen Sie Ihre Methoden eine bestimmte Sache tun


1

Die Vererbung der Zusammensetzung verengt den Änderungsvektor.

Stellen Sie sich vor, Ihre Lösung ist implementiert und Sie verwenden AClass an 300 verschiedenen Stellen der Codebasis. Jetzt müssen 45 der Aufrufe von method1 () so geändert werden, dass anstelle von do2 () eine do3 () -Methode aufgerufen wird.

Sie könnten do3 () auf der IAnInterface implementieren (hoffentlich können alle Implementierungen der Schnittstelle eine do3 () -Methode problemlos verarbeiten) und eine BClass erstellen, die do1 () oder do3 () im method1 () -Aufruf aufruft und die 45 Verweise auf ändert sowohl die AClass als auch die injizierte Abhängigkeit.

Jetzt Image, das dieselbe Änderung für do4 (), do5 (), ... implementiert.

Oder Sie könnten Folgendes tun ...

interface IFactoryCriteria {
    protected boolean check();
}

public final class DoerFactory() {
    protected final IAnInterfaceThatDoes createDoer( final IFactoryCriteria criteria ) {
        return criteria.check() ? new Doer1() : new Doer2();
    }
}

interface IAnInterfaceThatDoes {
    abstract void do();
}

public final class AClass implements IFactoryCriteria {
    private DoerFactory factory;
    public AClass(DoerFactory factory)
    {
        this.factory = factory;
    }

    protected void method1() {
        this.factory.createDoer( this ).do();
    }

    protected boolean check() {
        return true;//or false
    }
}

Jetzt müssen Sie nur noch eine zweite Fabrik implementieren, die entweder Doer1 oder Doer2 zurückgibt, und die Werkseinspritzung an 45 Stellen ändern. AClass ändert sich nicht, das IAnInterface ändert sich nicht und Sie können problemlos weitere Änderungen wie do4 (), do5 () verarbeiten.


1
+1 Ich mag diese Implementierung des Problems gegenüber dem von Ed vorgeschlagenen. Ich denke, dies macht das Problem mehr SRP und begrenzt die Abhängigkeiten, die AClass hat. Ich denke, meine Hauptfrage wäre das Für und Wider, IAnInterfaceThatDoes anstelle einer Factory-Klasse an AClass zu übergeben. Was passiert zum Beispiel, wenn die Logik zur Bestimmung des richtigen IAnInterfaceThatDoes außerhalb des Bereichs der Fabrik liegt?
Dreza

Ich habe die Factory zur Abhängigkeit gemacht, um die Exposition von IAnInterfaceThatDoes zu minimieren. Wenn der aufrufende Code den Zugriff auf diese Schnittstelle benötigt, können Sie ihn sicherlich verschieben. Ich kenne keine Situation, in der ich die Schnittstellenerstellung aus der Fabrik verschieben möchte. Ich würde sagen, wenn sich dies ändern muss, wäre die Implementierung einer DoerFactoryI-Schnittstelle mit der angezeigten createDoer-Methode ein weiterer Weg. Sie können dann eine Factory mithilfe eines DI-Frameworks oder Ähnlichem implementieren.
Heath Lilley

0

Warum nicht:

interface IAnInterface {
    abstract void do1();
    abstract void do2();
}

public final class AClass {
    private IAnInterface Dependent;
    public AClass(IAnInterface dependent)
    {
        this.Dependent = dependent;
    }

    protected void method1() {
        if(check()) {
            this.Dependent.do1();
        } else {
            this.Dependent.do2();
        }
    }
}

(Ich denke, dass das Finale das Äquivalent von "versiegelt" in C # ist. Bitte korrigiert mich jemand, wenn diese Syntax falsch ist.)

Abhängigkeitsinjektion / IoC bedeutet, dass Sie nur die Methoden erben können, die tatsächlich geändert werden sollen, was meines Erachtens Ihr Problem löst. Außerdem hört es sich nicht so an, als würde die Hauptklasse tatsächlich tun, was auch immer do1 und do2 sind, sondern sie delegiert die Aufrufe in ihre Unterklassen, basierend auf einer Bedingung: Sie brechen die Trennung von Bedenken!

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.