Gibt es gute Beispiele für das Erben von einer konkreten Klasse? [geschlossen]

73

Hintergrund:

Als Java-Programmierer erbe ich weitgehend (eher: implementiere) von Schnittstellen und entwerfe manchmal abstrakte Basisklassen. Ich hatte jedoch nie wirklich das Bedürfnis, eine konkrete (nicht abstrakte) Klasse zu unterordnen (in den Fällen, in denen ich dies tat, stellte sich später heraus, dass eine andere Lösung wie die Delegation besser gewesen wäre).

Jetzt habe ich das Gefühl, dass es fast keine Situation gibt, in der das Erben von einer konkreten Klasse angemessen ist. Zum einen scheint das Liskov-Substitutionsprinzip (LSP) für nicht triviale Klassen fast unmöglich zu sein; auch viele andere Fragen hier scheinen eine ähnliche Meinung zu wiederholen.

Also meine Frage:

In welcher Situation (falls vorhanden) ist es tatsächlich sinnvoll, von einer konkreten Klasse zu erben? Können Sie ein konkretes, reales Beispiel für eine Klasse geben, die von einer anderen konkreten Klasse erbt, bei der Sie der Meinung sind, dass dies angesichts der Einschränkungen das beste Design ist? Ich bin besonders an Beispielen interessiert, die den LSP erfüllen (oder an Beispielen, bei denen das Erfüllen des LSP unwichtig erscheint).

Ich habe hauptsächlich einen Java-Hintergrund, interessiere mich aber für Beispiele aus jeder Sprache.

sleske
quelle
4
Beim Codieren einer Hierarchie von Klassen, wie dem trivialen Hund, wird Animal erweitert. Normalerweise verwende ich die Vererbung ausgiebig, wenn ich Datenmodelle codiere. Für mich ist das Modellieren realer Dinge, wenn Vererbung ins Spiel kommt.
Mister Smith
Angenommen, ich habe verstanden, was Sie unter Delegierung verstehen: Ein guter Grund für die Verwendung von Unterklassen ist, dass es so schwierig ist, Klassen zu definieren, die alle Methoden einer Schnittstelle an eine andere Klasse delegieren - auch in Java und vielen anderen OOP-Sprachen. Daher ist es üblich, immer mehr Schnittstellen in Klassen zu akkumulieren, indem eine Hierarchie erstellt wird, in der jede Unterklasse eine andere Schnittstelle implementiert, die die Basisklasse nicht implementiert hat. Im Idealfall ist es jedoch sinnvoller, Schnittstellen zu delegieren.
Sinelaw
5
Es gibt den pathologischen Fall des Erbens von Objekt ...
Jens Schauder
12
@ZJR: Warum glaubst du, dass meine "Statistiken zur Wiederverwendung von Code" niedrig sind? Neben der Vererbung gibt es viele andere Möglichkeiten, Code wiederzuverwenden.
Sleske
1
Ich habe mich das Gleiche gefragt. Schauen Sie sich die Frage an, die ich gestellt habe: stackoverflow.com/questions/3351666/why-use-inheritance-at-all
KaptajnKold

Antworten:

20

Sie haben häufig eine Skelettimplementierung für eine Schnittstelle I. Wenn Sie Erweiterbarkeit ohne abstrakte Methoden anbieten können (z. B. über Hooks), ist es vorzuziehen, eine nicht abstrakte Skelettklasse zu haben, da Sie diese instanziieren können.

Ein Beispiel wäre eine Weiterleitungs-Wrapper-Klasse , um an ein anderes Objekt einer konkreten Klasse weiterleiten zu können, das Cimplementiert I, z. B. Dekoration oder einfache Wiederverwendung von Code ermöglicht, Cohne von erben zu müssen C. Sie finden ein solches Beispiel in Effective Java Item 16, bei dem die Komposition der Vererbung vorgezogen wird. (Ich möchte es hier wegen Urheberrechten nicht veröffentlichen, aber es leitet wirklich einfach alle Methodenaufrufe Ian die umschlossene Implementierung weiter).

DaveFar
quelle
9
+1 für Skelettimplementierungen. Ohne die "Adapter" -Klassen ( MouseAdapterusw.) von Swing, die NOOP-Implementierungen von Ereignis-Listener-Schnittstellen mit vielen Methoden bereitstellen, wäre das Schreiben von GUI-Komponenten noch schmerzhafter.
Gustafc
@gustafc: Gutes Beispiel. Beachten Sie jedoch, dass es sich um Swing-Adapterklassen handelt abstract, sodass dieses Beispiel meine Frage nicht wirklich beantwortet.
Sleske
Angenommen, jede Klasse foomüsste entweder abstrakt oder versiegelt sein, aber eine Deklaration einer konkreten vererbbaren Klasse würde sowohl eine abstrakte Klasse fooals auch eine versiegelte Klasse deklarieren, __impl_foodie geerbt hat, fooaber nichts anderes enthielt. Nehmen wir weiter an, das new foo()würde konvertiert werden new __impl_foo(). Würden solche Substitutionen eine andere Semantik als den Reflexionsnamen des konkreten Typs ändern?
Supercat
Ich denke nicht, außer Reflexionsklassennamen und Reflexionsmodifikatoren für die Klassen. Aber ich sehe keinen Vorteil.
DaveFar
18

Ich denke, das Folgende ist ein gutes Beispiel, wenn es angemessen sein kann:

public class LinkedHashMap<K,V>
    extends HashMap<K,V>

Ein weiteres gutes Beispiel ist die Vererbung von Ausnahmen:

public class IllegalFormatPrecisionException extends IllegalFormatException
public class IllegalFormatException extends IllegalArgumentException
public class IllegalArgumentException extends RuntimeException
public class RuntimeException extends Exception
public class Exception extends Throwable
Andrey
quelle
6
Die Vererbung von Ausnahmen ist in der Tat ein guter Punkt. Zu HashMap / LinkedHashMap: Warum ist dies Ihrer Meinung nach ein gutes Beispiel? Wäre es nicht sauberer gewesen, beide von einer Interface- / Abstract-Klasse erben zu lassen?
Sleske
1
@sleske Meiner Meinung nach ist es absolut legitim zu sagen, dass LinkedHashMap eine HashMap ist (in Bezug auf LSP). Ich sehe auch keine wirklichen Vorteile darin, hier eine zusätzliche Abstraktionsebene einzuführen. In diesem speziellen Fall handelt es sich bei der Vererbung jedoch nur um Implementierungsdetails. Für Verbraucher ist es nicht wichtig, dass LinkedHashMap HashMap erweitert (Sie würden Map map = new LinkedHashMap anstelle von HashMap map = new LinkedHashMap codieren).
Andrey
Ein Vorteil von AbstractHashMap mit den Unterklassen LinkedHM und HM besteht darin, dass Sie LinkedHM auch durch HM ersetzen können, nicht nur umgekehrt. Wenn Sie Map trotzdem immer verwenden, ist dies natürlich ein strittiger Punkt.
Sleske
14

Ein sehr häufiger Fall, den ich mir vorstellen kann, besteht darin, von grundlegenden Steuerelementen der Benutzeroberfläche wie Formularen, Textfeldern, Kombinationsfeldern usw. abzuleiten. Sie sind vollständig, konkret und können gut für sich allein stehen. Die meisten von ihnen sind jedoch auch sehr einfach, und manchmal ist ihr Standardverhalten nicht das, was Sie wollen. Zum Beispiel würde praktisch niemand eine Instanz eines unverfälschten Formulars verwenden, es sei denn, sie würden möglicherweise eine vollständig dynamische UI-Ebene erstellen.

Zum Beispiel habe ich in einer Software, die ich geschrieben habe und die kürzlich die relative Reife erreicht hat (was bedeutet, dass mir die Zeit ausgegangen ist, um mich hauptsächlich auf die Entwicklung zu konzentrieren :)), festgestellt, dass ich ComboBoxes die Funktion "Lazy Loading" hinzufügen muss, damit dies nicht der Fall ist. Es dauert 50 Jahre (in Computerjahren), bis das erste Fenster geladen ist. Ich brauchte auch die Fähigkeit, die verfügbaren Optionen in einer ComboBox automatisch zu filtern, basierend auf dem, was in einer anderen angezeigt wurde, und schließlich brauchte ich eine Möglichkeit, den Wert einer ComboBox in einem anderen bearbeitbaren Steuerelement zu "spiegeln" und eine Änderung in einem Steuerelement für das zu bewirken auch andere. Also habe ich die grundlegende ComboBox um diese zusätzlichen Funktionen erweitert und zwei neue Typen erstellt: LazyComboBox und dann MirroringComboBox. Beide basieren auf der vollständig zu wartenden, konkreten ComboBox-Steuerung. nur einige Verhaltensweisen überschreiben und ein paar andere hinzufügen. Sie sind nicht sehr locker gekoppelt und daher nicht zu FEST, aber die hinzugefügte Funktionalität ist allgemein genug, dass ich, wenn ich müsste, jede dieser Klassen von Grund auf neu schreiben könnte, um den gleichen Job zu erledigen, möglicherweise besser.

KeithS
quelle
12

Im Allgemeinen leite ich konkrete Klassen nur dann ab, wenn sie sich im Framework befinden. Ableiten von Appletoder JAppletals triviales Beispiel.

SL Barth - Monica wieder einsetzen
quelle
1
JPaneloder JComponentwenn Sie eine benutzerdefinierte Zeichnung für eine Swing-Komponente erstellen möchten.
Lukas Knuth
7

Dies ist ein Beispiel für eine aktuelle Implementierung, die ich durchführe.

In der OAuth 2-Umgebung ändert sich die Spezifikation ständig , da sich die Dokumentation noch im Entwurfsstadium befindet (zum Zeitpunkt des Schreibens befinden wir uns in Version 21).

Daher musste ich meine konkrete AccessTokenKlasse erweitern, um die verschiedenen Zugriffstoken aufzunehmen.

In früheren Entwürfen war kein token_typeFeld festgelegt, daher lautet das tatsächliche Zugriffstoken wie folgt:

public class AccessToken extends OAuthToken {

    /**
     * 
     */
    private static final long serialVersionUID = -4419729971477912556L;
    private String accessToken;
    private String refreshToken;
    private Map<String, String> additionalParameters;

    //Getters and setters are here
}

Mit den zurückgegebenen Zugriffstoken habe token_typeich jetzt

public class TokenTypedAccessToken extends AccessToken {

    private String tokenType;
    //Getter and setter are here...
}

Ich kann also beide zurückgeben und der Endbenutzer ist nicht klüger. :-)

Zusammenfassend: Wenn Sie eine angepasste Klasse möchten, die dieselbe Funktionalität wie Ihre konkrete Klasse aufweist, ohne die Struktur der konkreten Klasse zu ändern, empfehle ich, die konkrete Klasse zu erweitern.

Buhake Sindi
quelle
6

Ich habe hauptsächlich einen Java-Hintergrund, interessiere mich aber für Beispiele aus jeder Sprache.

Wie viele Frameworks nutzt ASP.NET die Vererbung stark, um das Verhalten zwischen Klassen zu teilen. Beispielsweise hat HtmlInputPassword diese Vererbungshierarchie:

System.Object
  System.Web.UI.Control
    System.Web.UI.HtmlControls.HtmlControl          // abstract
      System.Web.UI.HtmlControls.HtmlInputControl   // abstract
        System.Web.UI.HtmlControls.HtmlInputText
          System.Web.UI.HtmlControls.HtmlInputPassword

Darin sind Beispiele konkreter Klassen zu sehen, aus denen abgeleitet wird.

Wenn Sie ein Framework erstellen - und Sie sind sicher, dass Sie dies tun möchten -, möchten Sie möglicherweise eine schöne große Vererbungshierarchie.

AakashM
quelle
6

Ein anderer Anwendungsfall wäre das Überschreiben des Standardverhaltens:

Nehmen wir an, es gibt eine Klasse, die den Standard-Jaxb-Parser zum Parsen verwendet

public class Util{

    public void mainOperaiton(){..}
    protected MyDataStructure parse(){
        //standard Jaxb code 
    }
} 

Angenommen, ich möchte eine andere Bindung (Say XMLBean) für die Analyseoperation verwenden.

public class MyUtil extends Util{

    protected MyDataStructure parse(){
      //XmlBean code code 
    }
}

Jetzt kann ich die neue Bindung mit Code-Wiederverwendung der Superklasse verwenden.

Santosh
quelle
2
Guter Punkt. Natürlich ist hier eine Vererbung nur notwendig, weil die Basisklasse die Verwendung der Abhängigkeitsinjektion für den Parser törichterweise vernachlässigt ...
sleske
2
Im Allgemeinen erklärte das OP: "... eine andere Lösung wie die Delegation wäre besser gewesen."
Ben
1
Ich stimme Ben und Sleske zu, dies ist ein typischer Fall, in dem DI eine viel bessere Lösung als Vererbung wäre. Sie verwenden die Vererbung hier ausschließlich zur Wiederverwendung von Code.
Sverre Rabbelier
Ich stimme @SverreRabbelier zu. Dies scheint die Verwendung der Vererbung für die Wiederverwendung von Code zu sein, es sei denn, ich vermisse etwas, das JAXB und XMLBean innewohnt und das ich nicht gut kenne. Gibt es einen guten Grund, warum es die JAXB-Util-Klasse sein muss, die die gemeinsame Logik hat und um die XMLBean-Util-Klasse erweitert wird, oder könnte es leicht umgekehrt gewesen sein? Wenn es das letztere ist, bedeutet es für mich immer, dass ich nicht eine der beiden die andere erweitern lassen sollte, sondern eine abstrakte Basisklasse mit der gemeinsamen Logik und beide util-Klassen diese erweitern sollten.
SantiBailors
4

Das Dekorationsmuster , eine praktische Möglichkeit, einer Klasse zusätzliches Verhalten hinzuzufügen, ohne es zu allgemein zu gestalten, nutzt die Vererbung konkreter Klassen in hohem Maße. Es wurde hier bereits erwähnt, jedoch unter dem wissenschaftlichen Namen "Forwarding Wrapper Class".

MaDa
quelle
3

Viele Antworten, aber ich dachte, ich würde meine eigenen $ 0,02 hinzufügen.

Ich überschreibe Concreate-Klassen selten, aber unter bestimmten Umständen. Mindestens 1 wurde bereits erwähnt, wenn Framework-Klassen erweitert werden sollen. Zwei weitere kommen mit einigen Beispielen in den Sinn:

1) Wenn ich das Verhalten einer konkreten Klasse optimieren möchte. Manchmal möchte ich die Funktionsweise der konkreten Klasse ändern oder wissen, wann eine bestimmte Methode aufgerufen wird, damit ich etwas auslösen kann. Häufig definieren konkrete Klassen eine Hook-Methode, deren einzige Verwendung darin besteht, dass Unterklassen die Methode überschreiben.

Beispiel: Wir haben überschrieben, MBeanExporterweil wir in der Lage sein müssen, die Registrierung einer JMX-Bean aufzuheben :

public class MBeanRegistrationSupport {
    // the concrete class has a hook defined
    protected void onRegister(ObjectName objectName) {
    }

Unsere Klasse:

public class UnregisterableMBeanExporter extends MBeanExporter {
    @Override
    protected void onUnregister(ObjectName name) {
        // always a good idea
        super.onRegister(name);
        objectMap.remove(name);
    }

Hier ist ein weiteres gutes Beispiel. LinkedHashMapwurde entwickelt, um seine removeEldestEntryMethode überschreiben zu lassen.

private static class LimitedLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
    @Override
    protected boolean removeEldestEntry(Entry<K, V> eldest) {
        return size() > 1000;
    }

2) Wenn eine Klasse eine erhebliche Überlappung mit der konkreten Klasse aufweist, mit Ausnahme einiger Änderungen an der Funktionalität.

Beispiel: Mein ORMLite- Projekt verarbeitet persistierende LongObjektfelder und longprimitive Felder. Beide haben fast die gleiche Definition. LongObjectTypebietet alle Methoden, die beschreiben, wie die Datenbank mit longFeldern umgeht.

public class LongObjectType {
    // a whole bunch of methods

während LongTypeüberschreibt LongObjectTypeund nur eine einzige Methode optimiert, um zu sagen, dass Primitive behandelt werden.

public class LongType extends LongObjectType {
    ...
    @Override
    public boolean isPrimitive() {
        return true;
    }
}

Hoffe das hilft.

Grau
quelle
Über 1): Ja, das Bereitstellen von Hook-Methoden zum Überschreiben ist ein bekanntes Muster. Ich bin jedoch der Meinung, dass die Abhängigkeitsinjektion (oder die Verwendung von Aspekten) überlegen ist und normalerweise die bessere Alternative darstellt. Wenn Sie mit einer Klasse mit einer Hook-Methode feststecken, müssen Sie natürlich damit leben.
Sleske
Über 2): Dort hätte ich wirklich eine abstrakte Klasse und zwei Implementierungen erstellt. Ich bin der Meinung, dass dies auch die Beziehung der Klassen besser widerspiegelt (gemeinsame Basisfunktionalität, die auf zwei verschiedene Arten angewendet wird). Dies ist auch robuster, wenn LongObjectType später neue Methoden benötigt. Wenn Sie sie in die abstrakte Spitzenklasse einordnen, können Sie nicht vergessen, sie in beiden Unterklassen zu überschreiben. Mit der Version "LongType erweitert LongObjectType" können Sie LongObjectType hinzufügen und vergessen, in LongType zu überschreiben.
Sleske
Über 1): Die Abhängigkeitsinjektion ist in Ordnung, wenn sie von der Basisklasse unterstützt wird. Auch dies sind derzeit implementierte Klassen, über die ich spreche. Ich mag AOP wirklich nicht. Es ist unglaublich schwer zu debuggen und sicherlich viel weniger offensichtlich von einer Architektur Standpunkt.
Grau
Über 2): Natürlich gibt es Fälle, in denen ich abstrakte Klassen verwenden würde, aber das scheint einfach nicht richtig zu sein. Es gibt wirklich eine BaseDataType, die ich nicht beschrieben habe isPrimitive()und die falsch ist. Die LongObjectTypeUnterklasse beschreibt dann, wie Longs beibehalten und LongTypeüberschrieben werden isPrimitive(), um true zurückzugeben. Ein BaseLongObjectTypeGerechtes zu haben, scheint mir fremd zu sein.
Grau
2
  1. Das Erben einer konkreten Klasse ist nur möglich, wenn Sie die Funktionalität der Seitenbibliothek erweitern möchten.

  2. Zum Beispiel der realen Nutzung können Sie sich die Hierarchie von DataInputStream ansehen, die die DataInput-Schnittstelle für FilterInputStream implementiert.

Yarg
quelle
2

Ich fange an zu spüren, dass es fast keine Situation gibt, in der das Erben von einer konkreten Klasse angemessen ist.

Dies ist eine "fast". Versuchen Sie, eine zu schreibenohne zu verlängern Appletoder JApplet.

Hier ist ein zB aus dem Applet info. Seite .

/* <!-- Defines the applet element used by the appletviewer. -->
<applet code='HelloWorld' width='200' height='100'></applet> */
import javax.swing.*;

/** An 'Hello World' Swing based applet.

To compile and launch:
prompt> javac HelloWorld.java
prompt> appletviewer HelloWorld.java  */
public class HelloWorld extends JApplet {

    public void init() {
        // Swing operations need to be performed on the EDT.
        // The Runnable/invokeLater() ensures that happens.
        Runnable r = new Runnable() {
            public void run() {
                // the crux of this simple applet
                getContentPane().add( new JLabel("Hello World!") );
            }
        };
        SwingUtilities.invokeLater(r);
    }
}
Andrew Thompson
quelle
2

Ein weiteres gutes Beispiel wären Datenspeichertypen. Um ein genaues Beispiel zu nennen: Ein rot-schwarzer Baum ist ein spezifischerer Binärbaum, aber das Abrufen von Daten und anderen Informationen wie der Größe kann identisch behandelt werden. Natürlich sollte eine gute Bibliothek dies bereits implementiert haben, aber manchmal müssen Sie bestimmte Datentypen für Ihr Problem hinzufügen.

Ich entwickle gerade eine Anwendung, die Matrizen für die Benutzer berechnet. Der Benutzer kann Einstellungen vornehmen, um die Berechnung zu beeinflussen. Es gibt verschiedene Arten von Matrizen, die berechnet werden können, aber es gibt eine deutliche Ähnlichkeit, insbesondere in Bezug auf die Konfigurierbarkeit: Matrix A kann alle Einstellungen von Matrix B verwenden, verfügt jedoch über zusätzliche Parameter, die verwendet werden können. In diesem Fall habe ich von ConfigObjectB für mein ConfigObjectA geerbt und es funktioniert ziemlich gut.

Michael Schober
quelle
2

Im Allgemeinen ist es besser, von einer abstrakten Klasse zu erben als von einer konkreten Klasse. Eine konkrete Klasse muss eine Definition für ihre Datendarstellung bereitstellen, und einige Unterklassen benötigen eine andere Darstellung. Da eine abstrakte Klasse keine Datendarstellung bereitstellen muss, können zukünftige Unterklassen jede Darstellung verwenden, ohne befürchten zu müssen, mit der von ihnen geerbten in Konflikt zu geraten. Selbst ich habe nie eine Situation gefunden, in der ich der Meinung war, dass konkrete Vererbung notwendig ist. Es kann jedoch Situationen für eine konkrete Vererbung geben, insbesondere wenn Sie Abwärtskompatibilität für Ihre Software bereitstellen. In diesem Fall haben Sie sich vielleicht auf a spezialisiert class A, möchten aber, dass es konkret ist, da Ihre ältere Anwendung es möglicherweise verwendet.

amod
quelle
2

Ihre Bedenken spiegeln sich aus den von Ihnen genannten Gründen auch im klassischen Prinzip " Komposition gegenüber Vererbung bevorzugen " wider. Ich kann mich nicht erinnern, wann ich das letzte Mal von einer konkreten Klasse geerbt habe. Jeder allgemeine Code, der von untergeordneten Klassen wiederverwendet werden muss, muss fast immer abstrakte Schnittstellen für diese Klassen deklarieren. In dieser Reihenfolge versuche ich folgende Strategien zu bevorzugen:

  1. Zusammensetzung (keine Vererbung)
  2. Schnittstelle
  3. Abstrakte Klasse

Das Erben von einer konkreten Klasse ist wirklich nie eine gute Idee.

[BEARBEITEN] Ich werde diese Aussage qualifizieren, indem ich sage, dass ich keinen guten Anwendungsfall dafür sehe, wenn Sie die Kontrolle über die Architektur haben. Was tun, wenn Sie eine API verwenden, die dies erwartet? Aber ich verstehe die Designentscheidungen dieser APIs nicht. Die aufrufende Klasse sollte immer in der Lage sein, eine Abstraktion gemäß dem Prinzip der Abhängigkeitsinversion zu deklarieren und zu verwenden . Wenn eine untergeordnete Klasse über zusätzliche Schnittstellen verfügt, die verwendet werden müssen, müssen Sie entweder gegen DIP verstoßen oder ein hässliches Casting durchführen, um an diese Schnittstellen zu gelangen.

Dave Sims
quelle
1

aus dem gdata-Projekt :

com.google.gdata.client.Service dient als Basisklasse, die für bestimmte Arten von GData-Diensten angepasst werden kann.

Service Javadoc:

Die Serviceklasse repräsentiert eine Clientverbindung zu einem GData-Service. Es kapselt alle Interaktionen auf Protokollebene mit dem GData-Server und fungiert als Hilfsklasse für übergeordnete Entitäten (Feeds, Einträge usw.), die Vorgänge auf dem Server aufrufen und deren Ergebnisse verarbeiten.

Diese Klasse bietet die allgemeinen Funktionen auf Basisebene, die für den Zugriff auf einen GData-Dienst erforderlich sind. Es dient auch als Basisklasse, die für bestimmte Arten von GData-Diensten angepasst werden kann. Beispiele für unterstützte Anpassungen sind:

Authentifizierung - Implementierung eines benutzerdefinierten Authentifizierungsmechanismus für Dienste, die eine Authentifizierung erfordern und etwas anderes als die HTTP-Basis- oder Digest-Authentifizierung verwenden.

Erweiterungen - Definieren Sie erwartete Erweiterungen für Feed, Eintrag und andere Typen, die dem Service zugeordnet sind.

Formate - Definieren Sie zusätzliche benutzerdefinierte Ressourcendarstellungen, die von den Service- und clientseitigen Parsern und Generatoren verwendet oder erstellt werden können, um diese zu verarbeiten.

Swanliu
quelle
Gutes Beispiel. Für mich sieht diese Klasse jedoch so aus, als ob sie abstrakt sein sollte. Es hat sogar einen Konstruktor mit Standardsichtbarkeit, was bedeutet, dass Sie ihn nur innerhalb desselben Pakets aufrufen können. Um ihn von außen zu verwenden, müssen Sie ihn trotzdem in Unterklassen unterteilen.
Sleske
0

Ich finde die Java-Sammlungsklassen als sehr gutes Beispiel. Sie haben also eine AbstractCollection mit untergeordneten Elementen wie AbstractList, AbstractSet, AbstractQueue ... Ich denke, diese Hierarchie wurde gut entworfen. Um sicherzustellen, dass es keine Explosion gibt, gibt es die Collections-Klasse mit all ihren inneren statischen Klassen.

Skorpion
quelle
Das ist nur mein Punkt: Die Klassenhierarchie der Java-Auflistungsklassen verwendet abstrakte Klassen (AbstractCollection, AbstractList usw.). Ich interessierte mich für die Vererbung aus konkreten Klassen.
Sleske
Ach komm schon, hast du nicht bemerkt, dass diese abstrakten Klassen tatsächlich (ArrayList, HashSet) in der Java Collection-API selbst implementiert sind? Es gibt einige Implementierungen dieser abstrakten Klassen, die das Rückgrat bilden.
Scorpion
Ähm, ich fürchte, ich kann dir nicht folgen. Ja, die abstrakten Basisklassen haben konkrete Unterklassen in der API. Das war aber nicht meine Frage. Meine Frage betraf das Erben von einer konkreten Klasse , was nur sehr wenige der Collection-Klassen tun.
Sleske
Tatsächlich waren die einzigen Auflistungsklassen, die ich finden konnte, welche Unterklasse eine konkrete Klasse ist, LinkedHashSet (<HashSet) und Stack (<Vector). Stack ist wohl kein so gutes Design (da es sich um einen Vektor handelt, können Sie Elemente direkt auf sehr unstapelartige Weise ändern), und LinkedHashSet könnte genauso gut direkt von AbstractSet erben.
Sleske
Oh, ich habe Ihre Frage zu "Erben von Klassen" falsch verstanden und dabei nicht bemerkt, dass das Erben von konkreten Klassen nicht das ist, was Sie suchen. Das Erben von konkreten Klassen hat, wenn überhaupt, einen sehr begrenzten Umfang, und ich bin nicht überrascht, dass es nur sehr wenige Fälle gibt und solche, die ziemlich umstritten sind.
Scorpion
0

Dies tun Sie beispielsweise in GUI-Bibliotheken. Es macht wenig Sinn, von einer bloßen Komponente zu erben und an ein Panel zu delegieren. Es ist wahrscheinlich viel einfacher, direkt vom Panel zu erben.

Angel O'Sphere
quelle
0

Nur ein allgemeiner Gedanke. In abstrakten Klassen fehlt etwas. Es ist sinnvoll, wenn dies, was fehlt, in jeder abgeleiteten Klasse unterschiedlich ist. Möglicherweise haben Sie jedoch einen Fall, in dem Sie eine Klasse nicht ändern, sondern nur etwas hinzufügen möchten. Um Doppelarbeit zu vermeiden, würden Sie erben. Und wenn Sie beide Klassen benötigen, wäre dies eine Vererbung von einer konkreten Klasse.

Meine Antwort wäre also: In allen Fällen, in denen Sie wirklich nur etwas hinzufügen möchten. Vielleicht passiert das einfach nicht sehr oft.

Trilarion
quelle