it-swarm.com.de

Warum überhaupt Vererbung verwenden?

Ich weiß, die Frage war bereits besprochen , aber es scheint immer unter der Annahme zu stehen, dass Vererbung der Komposition zumindest manchmal vorzuziehen ist. Ich möchte diese Annahme in Frage stellen, um Verständnis zu erlangen.

Meine Frage lautet: Da können Sie mit der Objektzusammensetzung alles erreichen, was Sie mit der klassischen Vererbung erreichen können und da wird die klassische Vererbung sehr oft missbraucht [1] und da Die Objektzusammensetzung gibt Ihnen die Flexibilität, die Laufzeit des delegierten Objekts zu ändern.Warum würden Sie je die klassische Vererbung verwenden?

Ich kann irgendwie verstehen, warum Sie die Vererbung in einigen Sprachen wie Java und C++ empfehlen würden, die keine bequeme Syntax für die Delegierung bieten. In diesen Sprachen können Sie viel Tipparbeit sparen, indem Sie die Vererbung verwenden, wenn dies nicht eindeutig falsch ist. Andere Sprachen wie Objective C und Ruby bieten jedoch sowohl die klassische Vererbung als auch für die Delegierung sehr praktische Syntax. Die Programmiersprache Go ist meines Wissens die einzige Sprache, die entschieden hat, dass die klassische Vererbung mehr Mühe macht als sie sich lohnt, und unterstützt nur die Delegierung zur Wiederverwendung von Code.

Eine andere Möglichkeit, meine Frage zu formulieren, ist die folgende: Auch wenn Sie wissen, dass die klassische Vererbung nicht falsch ist, um ein bestimmtes Modell zu implementieren, reicht dieser Grund aus, es anstelle der Komposition zu verwenden?

[1] Viele Leute verwenden die klassische Vererbung, um Polymorphismus zu erreichen, anstatt ihre Klassen eine Schnittstelle implementieren zu lassen. Der Zweck der Vererbung ist die Wiederverwendung von Code, nicht der Polymorphismus. Darüber hinaus verwenden einige Leute die Vererbung, um ihr intuitives Verständnis einer "Ist-Eine" -Beziehung was oft problematisch sein kann zu modellieren.

Update

Ich möchte nur klarstellen, was ich genau meine, wenn ich über Vererbung spreche:

Ich spreche von die Art der Vererbung, bei der eine Klasse von einer teilweise oder vollständig implementierten Basisklasse erbt . Ich spreche nicht davon, von einer rein abstrakten Basisklasse zu erben, was dem Implementieren eines Interfaces gleichkommt, gegen das ich im Grunde nicht argumentiere.

Update 2

Ich verstehe, dass Vererbung der einzige Weg ist, um Polymorphismus in C++ zu erreichen. In diesem Fall ist es offensichtlich, warum Sie es verwenden müssen. Meine Frage beschränkt sich daher auf Sprachen wie Java oder Ruby, die unterschiedliche Möglichkeiten zur Erzielung von Polymorphismus bieten (Schnittstellen bzw. Typisierung von Enten).

58
KaptajnKold

Wenn Sie alles, was Sie nicht explizit überschrieben haben, an ein anderes Objekt delegieren, das dieselbe Schnittstelle implementiert (das "Basis" -Objekt), dann haben Sie im Grunde eine Greenspunn-Vererbung auf Komposition, jedoch (in den meisten Sprachen) mit viel mehr Ausführlichkeit und Kesselplatte. Der Zweck der Verwendung von Komposition anstelle von Vererbung besteht darin, dass Sie nur die Verhalten delegieren können, die Sie delegieren möchten. 

Wenn Sie möchten, dass das Objekt das gesamte Verhalten der Basisklasse verwendet, es sei denn, es wird explizit überschrieben, ist Vererbung die einfachste, am wenigsten ausführliche und einfachste Möglichkeit, sie auszudrücken.

10
dsimcha

Der Zweck der Vererbung ist die Wiederverwendung von Code, nicht der Polymorphismus. 

Das ist dein grundlegender Fehler. Fast genau das Gegenteil trifft zu. Der primary Zweck der (öffentlichen) Vererbung modelliert die Beziehungen zwischen den betreffenden Klassen. Polymorphismus ist ein großer Teil davon.

Bei korrekter Verwendung geht es bei der Vererbung nicht darum, vorhandenen Code wiederzuverwenden. Vielmehr geht es darum, by vorhandenen Code zu verwenden. Das heißt, wenn Sie über vorhandenen Code verfügen, der mit der vorhandenen Basisklasse arbeiten kann, wenn Sie eine neue Klasse von dieser vorhandenen Basisklasse ableiten, kann der andere Code jetzt auch automatisch mit der neuen abgeleiteten Klasse arbeiten.

Es ist möglich, die Vererbung für die Wiederverwendung von Code zu verwenden. Wenn Sie dies tun, sollte es sich jedoch normalerweise um private Vererbung und nicht um öffentliche Vererbung handeln. Wenn die von Ihnen verwendete Sprache die Delegierung gut unterstützt, sind die Chancen ziemlich groß, dass Sie selten Grund zur privaten Vererbung haben. OTOH, private Vererbung unterstützt einige Dinge, die Delegierung (normalerweise) nicht tut. Auch wenn der Polymorphismus in diesem Fall ein ausgesprochen sekundäres Problem ist, ist es can immer noch ein Problem - dh bei privater Vererbung können Sie von einer Basisklasse ausgehen, die fast ist Sie wollen und (sofern es möglich ist) die Teile überschreiben, die nicht ganz richtig sind.

Mit Delegation besteht die einzige Möglichkeit darin, die vorhandene Klasse genau so zu verwenden, wie sie ist. Wenn Sie nicht genau das tun, was Sie möchten, besteht die einzige wirkliche Wahl darin, diese Funktionalität vollständig zu ignorieren und von Grund auf neu zu implementieren. In manchen Fällen ist das kein Verlust, in anderen ist es jedoch recht groß. Wenn andere Teile der Basisklasse die polymorphe Funktion verwenden, können Sie durch private Vererbung nur die polymorphe Funktion überschreiben, und die anderen Teile verwenden Ihre überschriebene Funktion. Mit der Delegierung können Sie Ihre neue Funktionalität nicht einfach einbinden, sodass andere Teile der vorhandenen Basisklasse das überschreiben, was Sie überschrieben haben.

46
Jerry Coffin

Der Hauptgrund für die Verwendung von Vererbung ist nicht als Form der Komposition. Damit können Sie polymorphes Verhalten erhalten. Wenn Sie keinen Polymorphismus benötigen, sollten Sie die Vererbung wahrscheinlich nicht verwenden, zumindest in C++.

16
anon

Jeder weiß, dass Polymorphismus ein großer Vorteil der Vererbung ist. Ein weiterer Vorteil, den ich bei der Vererbung finde, ist, dass er eine Replik der realen Welt schafft. Im Pay-Roll-System handeln wir beispielsweise mit Managern, Entwicklern, Office-Boys usw., wenn wir alle diese Klassen mit Superklasse Employee erben. Es macht unser Programm im Kontext der realen Welt verständlicher, dass alle diese Klassen grundsätzlich Angestellte sind. Klassen enthalten jedoch nicht nur Methoden, sondern auch Attribute. Wenn wir also in der Employee -Klasse generische Attribute wie Employee. Bei der Verwendung von Vererbungssachen sollten wir jedoch bedenken, dass das grundlegende Konstruktionsprinzip "Identifizieren Sie die Aspekte Ihrer Anwendung, die sich unterscheiden und von den sich verändernden Aspekten trennen". Sie sollten niemals diejenigen Aspekte der Anwendung implementieren, die sich durch Vererbung ändern, sondern Komposition verwenden. Und für die Aspekte, die nicht veränderbar sind, sollten Sie Vererbung verwenden, wenn ein offensichtliches "Ist" eine Beziehung ist.

7
faisalbhagat

Vererbung ist zu bevorzugen, wenn:

  1. Sie müssen die gesamte API der Klasse freigeben, die Sie erweitern (mit Delegierung müssen Sie viele Delegierungsmethoden schreiben). Und Ihre Sprache bietet keine einfache Möglichkeit, "Delegieren aller unbekannten Methoden an" zu sagen.
  2. Sie müssen auf geschützte Felder/Methoden für Sprachen zugreifen, die kein Konzept von "Freunden" haben.
  3. Die Vorteile der Delegierung sind etwas geringer, wenn Ihre Sprache die Mehrfachvererbung zulässt
  4. In der Regel ist keine Delegierung erforderlich, wenn Ihre Sprache zur Laufzeit dynamisch von einer Klasse oder sogar von einer Instanz erbt. Sie brauchen es überhaupt nicht, wenn Sie gleichzeitig steuern können, welche Methoden (und wie sie offengelegt werden).

Meine Schlussfolgerung: Delegation ist eine Problemlösung für einen Fehler in einer Programmiersprache.

6
Aaron Digulla

Ich denke immer zweimal darüber nach, bevor ich Vererbung benutze, da es schnell schwierig werden kann. Allerdings gibt es viele Fälle, in denen einfach der eleganteste Code erzeugt wird.

4
ChaosPandion

Interfaces definieren nur, was ein Objekt tun kann und nicht wie. In einfachen Worten sind Schnittstellen also nur Verträge. Alle Objekte, die die Schnittstelle implementieren, müssen ihre eigene Implementierung des Vertrags definieren. In der praktischen Welt erhalten Sie separation of concern. Stellen Sie sich vor, Sie schreiben eine Anwendung, die sich mit verschiedenen Objekten befassen muss, die Sie nicht im Voraus kennen, und Sie müssen sich trotzdem mit ihnen beschäftigen. Sie wissen nur, was die verschiedenen Dinge tun sollen, die diese Objekte tun sollen. Sie definieren also eine Schnittstelle und erwähnen alle Vorgänge im Vertrag. Jetzt schreiben Sie Ihre Anwendung gegen diese Schnittstelle. Wer später Ihren Code oder Ihre Anwendung nutzen möchte, muss die Schnittstelle für das Objekt implementieren, damit er mit Ihrem System funktioniert. Ihre Schnittstelle zwingt ihr Objekt, zu definieren, wie jede im Vertrag definierte Operation ausgeführt werden soll. Auf diese Weise kann jeder Objekte schreiben, die Ihre Schnittstelle implementieren, damit sie sich problemlos an Ihr System anpassen können. Alles, was Sie wissen, ist, was getan werden muss, und es ist das Objekt, das definiert, wie es durchgeführt wird.

In der realen Entwicklung ist dies Die Praxis ist allgemein als .__ bekannt. Programming to Interface and not to Implementation

Schnittstellen sind nur Verträge oder Signaturen und sie wissen es nicht alles über Implementierungen.

Codierung gegen Schnittstelle bedeutet, dass der Clientcode immer ein Interface-Objekt enthält, das von einer Fabrik geliefert wird. Jede von der Factory zurückgegebene Instanz wäre vom Typ Interface, das von jeder Factory-Kandidatenklasse implementiert werden muss. Auf diese Weise macht sich das Client-Programm keine Sorgen um die Implementierung, und die Schnittstellensignatur bestimmt, was alle Operationen ausführen können. Dies kann verwendet werden, um das Verhalten eines Programms zur Laufzeit zu ändern. Es hilft Ihnen auch, aus Wartungssicht weitaus bessere Programme zu schreiben.

Hier ist ein grundlegendes Beispiel für Sie.

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

[STAThread]
static void Main()
{
    //This is your client code.
    ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
    speaker.Speak();
    Console.ReadLine();
}

public interface ISpeaker
{
    void Speak();
}

public class EnglishSpeaker : ISpeaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : ISpeaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak German.");
    }

    #endregion
}

public class SpanishSpeaker : ISpeaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    #endregion
}

alt text http://ruchitsurati.net/myfiles/interface.png

Was ist mit dem Vorlagenmuster? Angenommen, Sie haben eine Basisklasse mit einer Vielzahl von Punkten für anpassbare Richtlinien, aber Ein Strategiemuster ist aus mindestens einem der folgenden Gründe nicht sinnvoll:

  1. Die anpassbaren Richtlinien müssen über die Basisklasse Bescheid wissen, können nur mit der Basisklasse verwendet werden und sind in keinem anderen Kontext sinnvoll. Die Verwendung einer Strategie ist stattdessen machbar, aber eine PITA, da sowohl die Basisklasse als auch die Richtlinienklasse Verweise auf einander benötigen.

  2. Die Richtlinien sind so miteinander gekoppelt, dass es nicht sinnvoll ist, sie frei zu kombinieren. Sie sind nur in einer sehr begrenzten Teilmenge aller möglichen Kombinationen sinnvoll.

2
dsimcha

Der Hauptnutzen von klassischer Vererbung ist, wenn Sie über mehrere verwandte Klassen verfügen, die identische Logik für Methoden haben, die Instanzvariablen eigenschaften verarbeiten.

Es gibt wirklich drei Möglichkeiten, damit umzugehen:

  1. Erbe.
  2. Duplizieren Sie den Code (/ - Code Smell "Duplicated Code").
  3. Verschieben Sie die Logik in eine weitere Klasse (Code riecht nach "Lazy Class", "Middle Man", "Message Chains" und/oder "Unangemessene Intimität").

Nun kann es zu Missbrauch der Erbschaft kommen. Java hat beispielsweise die Klassen InputStream und OutputStream . Diese Unterklassen dienen zum Lesen/Schreiben von Dateien, Sockets, Arrays, Strings und mehrere zum Einschließen anderer Eingabe-/Ausgabeströme. Basierend auf dem, was sie tun, sollten dies eher Schnittstellen als Klassen gewesen sein.

0
Powerlord

Sie schrieben:

[1] Viele Leute verwenden klassische Vererbung, um Polymorphismus zu erreichen anstatt ihre Klassen zu lassen eine Schnittstelle implementieren. Der Zweck von Vererbung ist Wiederverwendung von Code, nicht Polymorphismus. Darüber hinaus einige Leute Vererbung verwenden, um ihre .__ zu modellieren. intuitives Verstehen eines "is-a" Beziehung, die oft sein kann problematisch.

In den meisten Sprachen ist die Grenze zwischen 'Implementieren einer Schnittstelle' und 'Ableiten einer Klasse von einer anderen' sehr schmal. Wenn Sie in Sprachen wie C++ eine Klasse B von einer Klasse A ableiten und A eine Klasse ist, die nur aus rein virtuellen Methoden besteht, implementieren Sie are eine Schnittstelle.

Bei der Vererbung geht es um interface-Wiederverwendung, nicht um Implementierungswiederverwendung. Es geht um nicht um Wiederverwendung von Code, wie Sie oben geschrieben haben.

Vererbung soll, wie Sie richtig betonen, eine IS-A-Beziehung modellieren (die Tatsache, dass viele Menschen dies falsch verstehen, hat nichts mit Vererbung an sich zu tun). Sie können auch "BEHAVES-LIKE-A" sagen. Nur weil etwas eine IS-A-Beziehung zu etwas anderem hat, bedeutet dies nicht, dass es denselben (oder sogar ähnlichen) Code verwendet, um diese Beziehung zu erfüllen.

Vergleichen Sie dieses C++ - Beispiel, das verschiedene Arten der Datenausgabe implementiert. zwei Klassen verwenden (öffentliche) Vererbung, um auf polymorph zuzugreifen:

struct Output {
  virtual bool readyToWrite() const = 0;
  virtual void write(const char *data, size_t len) = 0;
};

struct NetworkOutput : public Output {
  NetworkOutput(const char *Host, unsigned short port);

  bool readyToWrite();
  void write(const char *data, size_t len);
};

struct FileOutput : public Output {
  FileOutput(const char *fileName);

  bool readyToWrite();
  void write(const char *data, size_t len);
};

Stellen Sie sich jetzt vor, ob dies Java wäre. 'Ausgabe' war keine Struktur, sondern eine 'Schnittstelle'. Es könnte als "beschreibbar" bezeichnet werden. Anstelle von "public Output" würden Sie sagen "implementiert Writable". Was ist der Unterschied beim Design?

Keiner.

0
Frerich Raabe

Ganz und gar nicht OOP, aber Komposition bedeutet in der Regel einen zusätzlichen Cache-Miss. Es kommt darauf an, aber die Daten näher zu haben, ist ein Plus.

Im Allgemeinen lehne ich es ab, in einige religiöse Kämpfe einzusteigen. Nach Ihrem eigenen Urteilsvermögen und Stil ist das Beste, was Sie bekommen können.

0
bestsss

Wenn Sie gefragt haben:

Auch wenn Sie wissen, dass die klassische Vererbung nicht falsch ist, um ein bestimmtes Modell zu implementieren, ist das Grund genug, es anstelle von Komposition zu verwenden?

Die Antwort ist nein. Wenn das Modell falsch ist (Vererbung verwendet), ist es falsch, es zu verwenden, egal was.

Hier sind einige Probleme mit der Vererbung, die ich gesehen habe:

  1. Sie müssen immer den Laufzeittyp abgeleiteter Klassenzeiger testen, um zu sehen, ob sie hochgestuft werden können.
  2. Dieses "Testen" kann auf verschiedene Arten erreicht werden. Möglicherweise verfügen Sie über eine Art virtuelle Methode, die eine Klassenkennung zurückgibt. Andernfalls müssen Sie möglicherweise RTTI (Laufzeittypidentifikation) implementieren (zumindest in c/c ++), was zu einem Leistungstreffer führen kann.
  3. klassentypen, die sich nicht "werfen" lassen, können potenziell problematisch sein. 
  4. Es gibt viele Möglichkeiten, Ihren Klassentyp im Vererbungsbaum auf und ab zu verschieben.
0
C Johnson

Eine der nützlichsten Methoden, die ich zur Verwendung der Vererbung sehe, ist in GUI-Objekten.

0
Brad Barker