41 Stimmen

Was ist das Problem der fragilen Basisklasse?

Was ist das Problem der fragilen Basisklasse in Java?

41voto

Colin Pickard Punkte 44501

Eine fragile Basisklasse ist ein häufiges Problem bei der Vererbung, das für Java und jede andere Sprache gilt, die Vererbung unterstützt.

Kurz gesagt, die Basisklasse ist die Klasse, von der Sie erben, und sie wird oft als fragil bezeichnet, weil Änderungen an dieser Klasse unerwartete Auswirkungen auf die Klassen haben können, die von ihr erben.

Es gibt einige wenige Methoden, um dies abzumildern, aber keine einfache Methode, um es vollständig zu vermeiden und trotzdem Vererbung zu verwenden. Sie können verhindern, dass andere Klassen von einer Klasse erben, indem Sie die Klassendeklaration als final in Java.

Um die schlimmsten dieser Probleme zu vermeiden, empfiehlt es sich, alle Klassen als endgültig zu kennzeichnen, es sei denn, Sie beabsichtigen ausdrücklich, von ihnen zu erben. Wenn Sie beabsichtigen, von einer Klasse zu erben, entwerfen Sie sie so, als ob Sie eine API entwerfen würden: Verstecken Sie alle Implementierungsdetails; seien Sie streng bei dem, was Sie ausgeben, und vorsichtig bei dem, was Sie akzeptieren, und dokumentieren Sie das erwartete Verhalten der Klasse im Detail.

16voto

stacker Punkte 65961

Eine Basisklasse wird als fragil bezeichnet, wenn Änderungen an ihr eine abgeleitete Klasse zerstören.

class Base{
    protected int x;
    protected void m(){
       x++;
    }

    protected void n(){
      x++;      // <- defect 
      m();
     }
 }

class Sub extends Base{
        protected void m(){
            n();
        }
    }

3voto

AndroidLover Punkte 1113

Alles, was Colin Pickard gesagt hat, ist wahr, aber hier möchte ich einige der besten Praktiken hinzufügen, wenn Sie Code schreiben, der diese Art von Problem verursachen kann, und besonders, wenn Sie ein Framework oder eine Bibliothek erstellen.

  1. Machen Sie alle Ihre konkreten Klassen standardmäßig final, weil Sie wahrscheinlich nicht wollen, dass sie vererbt werden. Dieses Verhalten finden Sie in vielen Sprachen, z. B. in Kotlin. Außerdem können Sie, wenn Sie sie erweitern müssen, jederzeit die final Stichwort. Auf diese Weise wird das Fehlen von final für eine Klasse kann als Warnung interpretiert werden, sich nicht auf eine bestimmte Funktionalität für andere Methoden auf this die nicht private y/o final .

  2. Bei Klassen, die nicht als endgültig gekennzeichnet werden können, sollten Sie so viele Methoden wie möglich erstellen final um sicherzustellen, dass sie nicht von Unterklassen geändert werden. Außerdem sollten Sie keine Methoden freigeben, die nicht überschrieben werden sollen - bevorzugen Sie private über protected . Gehen Sie davon aus, dass jede Methode, die nicht private y/o final überschrieben werden und sicherstellen, dass der Code Ihrer Oberklasse weiterhin funktioniert.

  3. Versuchen Sie, keine Vererbungsbeziehung ("Bar ist ein Foo") zu verwenden. Verwenden Sie stattdessen eine Hilfsbeziehung ("Bar verwendet ein Foo") zwischen Ihren Klassen. Verwenden Sie Schnittstellen anstelle von abstrakten Klassen, um sicherzustellen, dass die Klassen, die diese Hilfsklasse verwenden, eine einheitliche Schnittstelle haben.

  4. Denken Sie daran, dass fast* jeder extends kann ersetzt werden durch implements . Dies ist der Fall, wenn Sie eine Standardimplementierung wünschen; ein Beispiel für eine solche Konvertierung ist unten dargestellt:

Alter Code:

class Superclass {
    void foo() {
        // implementation
    }
    void bar() {
        // implementation
    }
}

class Subclass extends Superclass {
    // don't override `foo`
    // override `bar`
    @Override
    void bar() {
        // new implementation
    }
}

Neuer Code:

// Replace the superclass with an interface.
public interface IClass {
    void foo();
    void bar();
}

// Put any implementation in another, final class.
final class Superclass implements IClass {
    public void foo() {
        // implementation for superclass
    }
    public void bar() {
        // implementation for superclass
    }
}

// Instead of `extend`ing the superclass and overriding methods,
// use an instance of the implementation class as a helper.
// Naturally, the subclass can also forgo the helper and
// implement all the methods for itself.
class Subclass implements IClass {
    private Superclass helper = new Superclass();

    // Don't override `foo`.
    public void foo() {
        this.helper.foo();
    }

    // Override `bar`.
    public void bar() {
        // Don't call helper; equivalent of an override.
        // Or, do call helper, but the helper's methods are
        // guaranteed to be its own rather than possibly
        // being overridden by yours.
    }
}

Dies hat den Vorteil, dass die Methoden der Oberklasse sicher sein können, dass sie miteinander arbeiten, aber gleichzeitig können Sie Methoden in Ihrer Unterklasse überschreiben.

*Wenn Sie tatsächlich wollten, dass die Oberklasse Ihre überschriebene Methode verwendet, haben Sie mit diesem Ansatz Pech gehabt, es sei denn, Sie wollen auch alle diese Methoden in der "Unterklasse" neu implementieren. Abgesehen davon kann es verwirrend sein, wenn die Oberklasse die Unterklasse aufruft, so dass es gut sein kann, diese Art der Verwendung neu zu bewerten, ungeachtet der Inkompatibilität mit diesem Ansatz.

3voto

bastiat Punkte 1557

Es wird im folgenden Artikel von Allen Holub auf JavaWorld ausführlich beschrieben

Warum Ausdehnung böse ist. Verbessern Sie Ihren Code, indem Sie konkrete Basisklassen durch Schnittstellen ersetzen

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X