Warum sind Vererbung und Polymorphismus so weit verbreitet?


18

Je mehr ich über verschiedene Programmierparadigmen wie die funktionale Programmierung lerne, desto mehr beginne ich, die Weisheit von OOP-Konzepten wie Vererbung und Polymorphismus in Frage zu stellen. Ich habe zum ersten Mal in der Schule etwas über Vererbung und Polymorphismus gelernt, und damals schien Polymorphismus eine wunderbare Möglichkeit zu sein, generischen Code zu schreiben, der eine einfache Erweiterbarkeit ermöglichte.

Aber angesichts der dynamischen und statischen Typisierung von Enten sowie funktionaler Merkmale wie Funktionen höherer Ordnung habe ich begonnen, Vererbung und Polymorphismus als eine unnötige Einschränkung zu betrachten, die auf einem fragilen Satz von Beziehungen zwischen Objekten beruht. Die allgemeine Idee hinter Polymorphismus ist, dass Sie eine Funktion einmal schreiben und später Ihrem Programm neue Funktionen hinzufügen können, ohne die ursprüngliche Funktion zu ändern. Sie müssen lediglich eine weitere abgeleitete Klasse erstellen, die die erforderlichen Methoden implementiert.

Dies ist jedoch viel einfacher durch Eingabe von Enten zu erreichen, unabhängig davon, ob es sich um eine dynamische Sprache wie Python oder eine statische Sprache wie C ++ handelt.

Betrachten Sie als Beispiel die folgende Python-Funktion, gefolgt von ihrem statischen C ++ - Äquivalent:

def foo(obj):
   obj.doSomething()

template <class Obj>
void foo(Obj& obj)
{
   obj.doSomething();
}

Das OOP-Äquivalent wäre etwa der folgende Java-Code:

public void foo(DoSomethingable obj)
{
  obj.doSomething();
}

Der Hauptunterschied besteht natürlich darin, dass die Java-Version die Erstellung einer Schnittstelle oder einer Vererbungshierarchie erfordert, bevor sie funktioniert. Die Java-Version ist daher aufwändiger und weniger flexibel. Außerdem finde ich, dass die meisten Vererbungshierarchien in der Praxis etwas instabil sind. Wir haben alle die erfundenen Beispiele für Formen und Tiere gesehen, aber in der realen Welt ist es schwierig, eine Arbeit zu erledigen, wenn sich die Geschäftsanforderungen ändern und neue Funktionen hinzugefügt werden, bevor Sie die "Ist-Eine" -Beziehung zwischen ihnen wirklich ausdehnen müssen Unterklassen oder modifizieren / refaktorieren Sie Ihre Hierarchie, um weitere Basisklassen oder Schnittstellen einzubeziehen, um neuen Anforderungen gerecht zu werden. Mit Duck Typing brauchen Sie sich keine Gedanken über das Modellieren zu machen - Sie müssen sich nur um das Modellieren kümmern Funktionalität Sie benötigen.

Vererbung und Polymorphismus sind jedoch so beliebt, dass ich bezweifle, dass es übertrieben wäre, sie als die vorherrschende Strategie für Erweiterbarkeit und Wiederverwendung von Code zu bezeichnen. Warum sind Vererbung und Polymorphismus so erfolgreich? Übersehe ich einige gravierende Vorteile, die Vererbung / Polymorphismus gegenüber dem Typisieren von Enten haben?


Da ich kein Python-Experte bin, muss ich fragen: Was würde passieren, wenn objich keine doSomethingMethode hätte? Wird eine Ausnahme ausgelöst? Passiert nichts
FrustratedWithFormsDesigner

6
@Frustriert: Es wird eine sehr klare, spezifische Ausnahme ausgelöst.
Dsimcha

11
Wird beim Tippen von Enten nicht nur der Polymorphismus auf elf geschätzt? Ich vermute, Sie sind nicht frustriert über OOP, sondern über die statisch typisierten Inkarnationen. Ich kann das wirklich verstehen, aber das macht OOP als Ganzes nicht zu einer schlechten Idee.

3
Lesen Sie zuerst "weit" als "wild" ...

Antworten:


22

Ich stimme Ihnen größtenteils zu, aber zum Spaß spiele ich Devil's Advocate. Explizite Schnittstellen bieten einen zentralen Ort, an dem Sie nach einem explizit formal festgelegten Vertrag suchen und erfahren, was ein Typ tun soll. Dies kann wichtig sein, wenn Sie nicht der einzige Entwickler eines Projekts sind.

Darüber hinaus können diese expliziten Schnittstellen effizienter implementiert werden als die Eingabe von Enten. Ein virtueller Funktionsaufruf hat kaum mehr Overhead als ein normaler Funktionsaufruf, außer dass er nicht eingebunden werden kann. Das Tippen von Enten ist mit erheblichem Aufwand verbunden. Die strukturelle Typisierung im C ++ - Stil (unter Verwendung von Vorlagen) kann große Mengen an aufgeblähter Objektdatei erzeugen (da jede Instanziierung auf Objektdateiebene unabhängig ist) und funktioniert nicht, wenn Sie zur Laufzeit Polymorphismus benötigen, nicht zur Kompilierungszeit.

Fazit: Ich bin damit einverstanden, dass Vererbung und Polymorphismus im Java-Stil eine PITA sein können und Alternativen häufiger verwendet werden sollten, aber sie haben immer noch ihre Vorteile.


29

Vererbung und Polymorphismus sind weit verbreitet, da sie bei bestimmten Arten von Programmierproblemen funktionieren.

Es ist nicht so, dass sie in den Schulen weit verbreitet sind, das ist umgekehrt: Sie werden in den Schulen weit verbreitet unterrichtet, weil die Leute (auch bekannt als der Markt) festgestellt haben, dass sie besser funktionieren als die alten Werkzeuge, und deshalb haben die Schulen angefangen, sie zu unterrichten. [Anekdote: Als ich zum ersten Mal OOP lernte, war es äußerst schwierig, ein College zu finden, das eine OOP-Sprache unterrichtete. Zehn Jahre später war es schwierig, ein College zu finden, das keine OOP-Sprache unterrichtete.]

Du sagtest:

Die allgemeine Idee hinter Polymorphismus ist, dass Sie eine Funktion einmal schreiben und später Ihrem Programm neue Funktionen hinzufügen können, ohne die ursprüngliche Funktion zu ändern. Sie müssen lediglich eine weitere abgeleitete Klasse erstellen, die die erforderlichen Methoden implementiert

Ich sage:

Nein, das ist es nicht

Was Sie beschreiben, ist nicht Polymorphismus, sondern Vererbung. Kein Wunder, dass Sie OOP-Probleme haben! ;-)

Sichern Sie einen Schritt: Polymorphismus ist ein Vorteil der Nachrichtenübermittlung. es bedeutet einfach, dass jedes Objekt frei ist, auf eine Nachricht auf seine eigene Weise zu antworten.

Also ... Duck Typing ist (oder vielmehr ermöglicht) Polymorphismus

Der Kern Ihrer Frage scheint nicht zu sein, dass Sie OOP nicht verstehen oder dass Sie es nicht mögen , sondern dass Sie es nicht mögen, Schnittstellen zu definieren . Das ist in Ordnung, und solange Sie vorsichtig sind, werden die Dinge gut funktionieren. Der Nachteil ist, dass Sie einen Fehler - beispielsweise das Weglassen einer Methode - erst zur Laufzeit feststellen können.

Das ist eine statische vs dynamische Sache, eine Diskussion, die so alt ist wie Lisp und nicht auf OOP beschränkt ist.


2
+1 für "Duck Typing ist Polymorphismus". Polymorphismus und Vererbung sind viel zu lange unbewusst miteinander verknüpft, und strikte statische Typisierung ist der einzig wahre Grund, warum sie jemals sein mussten.
CHAO

+1. Ich denke, die Unterscheidung zwischen statisch und dynamisch sollte mehr als eine Randnotiz sein - sie ist der Kern der Unterscheidung zwischen Ententypisierung und Java-artigem Polymorphismus.
Sava B.

8

Ich habe angefangen, Vererbung und Polymorphismus als eine unnötige Einschränkung aufzufassen, die auf einer fragilen Menge von Beziehungen zwischen Objekten beruht.

Warum?

Die Vererbung (mit oder ohne Eingabe von Enten) sichert die Wiederverwendung einer gemeinsamen Funktion. Wenn es üblich ist, können Sie sicherstellen, dass es in Unterklassen konsistent wiederverwendet wird.

Das ist alles. Es gibt keine "unnötige Einschränkung". Es ist eine Vereinfachung.

Ähnlich ist Polymorphismus das, was "Ententypisierung" bedeutet. Gleiche Methoden. Viele Klassen mit identischer Schnittstelle, aber unterschiedlichen Implementierungen.

Es ist keine Einschränkung. Es ist eine Vereinfachung.


7

Vererbung wird missbraucht, aber auch das Tippen von Enten. Beides kann und kann zu Problemen führen.

Mit starker Eingabe erhalten Sie viele "Komponententests", die zur Kompilierungszeit durchgeführt werden. Beim Entenschreiben muss man sie oft schreiben.


3
Ihr Kommentar zum Testen gilt nur für die dynamische Eingabe von Enten. Statisches Duck-Typing im C ++ - Stil (mit Vorlagen) gibt Ihnen Fehler bei der Kompilierung. (Zugegeben, C ++ - Template-Fehler sind zwar schwer zu entziffern, aber das ist ein ganz anderes Problem.)
Channel72

2

Das Gute daran, Dinge in der Schule zu lernen, ist, dass man sie lernt. Das nicht so Gute ist, dass Sie sie vielleicht ein wenig zu dogmatisch akzeptieren, ohne zu verstehen, wann sie nützlich sind und wann nicht.

Wenn Sie es dann dogmatisch gelernt haben, können Sie später ebenso dogmatisch in die andere Richtung "rebellieren". Das ist auch nicht gut.

Wie bei solchen Ideen ist es am besten, einen pragmatischen Ansatz zu wählen. Entwickeln Sie ein Verständnis dafür, wo sie passen und wo nicht. Und ignorieren Sie alle Möglichkeiten, die sie überverkauft haben.


2

Ja, statische Typisierung und Schnittstellen sind Einschränkungen. Aber alles, was seit der Erfindung der strukturierten Programmierung (dh "als schädlich eingestuft") passiert ist, hat uns gezwungen, uns einzuschränken. Onkel Bob hat dies in seinem Videoblog hervorragend aufgenommen .

Nun kann man argumentieren, dass Verengung schlecht ist, aber andererseits bringt sie Ordnung, Kontrolle und Vertrautheit in ein ansonsten sehr komplexes Thema.

Die Lockerung von Einschränkungen durch (erneute) Einführung dynamischer Typisierung und sogar direkten Speicherzugriff ist ein sehr leistungsfähiges Konzept, das es jedoch für viele Programmierer schwieriger machen kann, damit umzugehen. Vor allem Programmierer waren es gewohnt, sich für einen Großteil ihrer Arbeit auf den Compiler und die Typensicherheit zu verlassen.


1

Vererbung ist eine sehr starke Beziehung zwischen zwei Klassen. Ich denke nicht, dass Java stärker ist. Deshalb sollten Sie es nur verwenden, wenn Sie es ernst meinen. Öffentliche Vererbung ist eine "ist-eine" Beziehung, keine "normalerweise ist-eine". Es ist wirklich sehr, sehr einfach, Vererbung zu überbeanspruchen und mit einem Durcheinander fertig zu werden. In vielen Fällen wird die Vererbung verwendet, um "has-a" oder "takes-functional-from-a" darzustellen, und dies geschieht normalerweise besser durch Komposition.

Polymorphismus ist eine direkte Konsequenz der "Ist-Ist" -Beziehung. Wenn Derived von Base erbt, ist jede Derived-Base eine Base. Daher können Sie eine Derived überall dort verwenden, wo Sie eine Base verwenden möchten. Wenn dies in einer Vererbungshierarchie keinen Sinn ergibt, ist die Hierarchie falsch und es wird wahrscheinlich zu viel vererbt.

Duck Typing ist eine nette Funktion, aber der Compiler warnt Sie nicht, wenn Sie es missbrauchen. Wenn Sie zur Laufzeit keine Ausnahmen behandeln müssen, müssen Sie sich vergewissern, dass Sie jedes Mal die richtigen Ergebnisse erzielen. Es kann einfacher sein, nur eine statische Vererbungshierarchie zu definieren.

Ich bin kein wirklicher Fan des statischen Tippens (ich halte es oft für eine Form der vorzeitigen Optimierung), aber es beseitigt einige Fehlerklassen, und viele Leute denken, diese Klassen sind es wert, beseitigt zu werden.

Wenn Sie dynamisches Schreiben und Entenschreiben besser mögen als statisches Schreiben und definierte Vererbungshierarchien, ist das in Ordnung. Der Java-Weg hat jedoch seine Vorteile.


0

Ich habe festgestellt, dass ich mit zunehmender Verwendung von C # -Verschlüssen weniger traditionelle OOP-Operationen durchführe. Früher war die Vererbung die einzige Möglichkeit, die Implementierung einfach zu teilen. Ich denke, sie wurde häufig überstrapaziert und die konzeptionellen Grenzen wurden zu weit verschoben.

Während Sie können in der Regel Verschlüsse verwenden die meisten zu tun , was Sie mit Vererbung tun würde, kann es auch hässlich.

Grundsätzlich ist es eine Situation, in der das richtige Werkzeug für den Job ist: Traditionelle OOP können sehr gut funktionieren, wenn Sie ein Modell haben, das dazu passt, und Verschlüsse können sehr gut funktionieren, wenn Sie dies nicht tun.


-1

Die Wahrheit liegt irgendwo in der Mitte. Ich mag, wie C # 4.0 als statisch typisierte Sprache "Enten-Typisierung" durch "dynamisches" Schlüsselwort unterstützt.


-1

Vererbung, auch wenn die Vernunft aus FP-Sicht ein großartiges Konzept ist; Dies spart nicht nur viel Zeit, sondern gibt auch der Beziehung zwischen bestimmten Objekten in Ihrem Programm eine Bedeutung.

public class Animal {
    public virtual string Sound () {
        return "Some Sound";
    }
}

public class Dog : Animal {
    public override string Sound () {
        return "Woof";
    }
}

public class Cat : Animal {
    public override string Sound () {
        return "Mew";
    }
}

public class GoldenRetriever : Dog {

}

Hier hat die Klasse GoldenRetrieverdas gleiche Soundwie Dogkostenlos danke an Vererbung.

Ich werde dasselbe Beispiel mit meinem Haskell-Level schreiben, damit Sie den Unterschied erkennen können

data Animal = Animal | Dog | Cat | GoldenRetriever

sound :: Animal -> String
sound Animal = "Some Sound"
sound Dog = "Woof"
sound Cat = "Mew"
sound GoldenRetriever = "Woof"

Hier kommt man nicht umhin, angeben soundzu müssen GoldenRetriever. Das Einfachste im Allgemeinen wäre zu

sound GoldenRetriever = sound Dog

aber nur imagen wenn du 20 funktionen hast! Wenn es einen Haskell-Experten gibt, zeigen Sie uns bitte einen einfacheren Weg.

Das heißt, es wäre großartig, gleichzeitig Mustervergleich und Vererbung zu haben, wobei eine Funktion standardmäßig die Basisklasse verwendet, wenn die aktuelle Instanz nicht über die Implementierung verfügt.


5
Ich schwöre, Animalist das Anti-Tutorial von OOP.
Telastyn

@Telastyn Ich habe das Beispiel von Derek Banas auf Youtube gelernt. Toller Lehrer. Meiner Meinung nach ist es sehr einfach zu visualisieren und zu überlegen. Sie können den Beitrag mit einem besseren Beispiel bearbeiten.
Cristian Garcia

Dies scheint nichts Wesentliches zu den vorherigen 10 Antworten hinzuzufügen
gnat

@gnat Während die "Substanz" bereits da draußen ist, findet eine Person, die neu in dem Thema ist, möglicherweise ein Beispiel, das leichter zu verstehen ist.
Cristian Garcia

2
Tiere und Formen sind aus mehreren Gründen, die ad nauseum auf dieser Site diskutiert wurden, wirklich schlechte Anwendungen des Polymorphismus. Grundsätzlich ist es bedeutungslos, eine Katze als "Tier" zu bezeichnen, da es kein konkretes "Tier" gibt. Was Sie wirklich modellieren wollen, ist Verhalten, nicht Identität. Die Definition einer Schnittstelle "CanSpeak", die als Miau oder Bark implementiert werden kann, ist sinnvoll. Das Definieren einer Schnittstelle "HasArea", die ein Quadrat und ein Kreis implementieren können, ist sinnvoll, aber keine generische Form.
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.