prototypbasierte vs. klassenbasierte Vererbung

208

In JavaScript ist jedes Objekt gleichzeitig eine Instanz und eine Klasse. Für die Vererbung können Sie eine beliebige Objektinstanz als Prototyp verwenden.

In Python, C ++ usw. gibt es Klassen und Instanzen als separate Konzepte. Um eine Vererbung durchzuführen, müssen Sie die Basisklasse verwenden, um eine neue Klasse zu erstellen, die dann zum Erstellen abgeleiteter Instanzen verwendet werden kann.

Warum ist JavaScript in diese Richtung gegangen (prototypbasierte Objektorientierung)? Was sind die Vor- und Nachteile von prototypbasiertem OO gegenüber herkömmlichem klassenbasiertem OO?

Stefano Borini
quelle
10
JavaScript wurde von Self beeinflusst, der ersten Sprache mit prototypischer Vererbung. Zu dieser Zeit war das klassische Erbe der letzte Schrei, der zuerst in Simula eingeführt wurde. Die klassische Vererbung war jedoch zu kompliziert. Dann hatten David Ungar und Randall Smith eine Offenbarung, nachdem sie GEB gelesen hatten: "Das spezifischste Ereignis kann als allgemeines Beispiel für eine Klasse von Ereignissen dienen." Sie erkannten, dass Klassen für die objektorientierte Programmierung nicht erforderlich sind. Daher wurde das Selbst geboren. Um zu wissen, wie prototypische Vererbung besser ist als klassische Vererbung, lesen Sie Folgendes : stackoverflow.com/a/16872315/783743 =)
Aadit M Shah
@AaditMShah Was / wer ist GEB?
Alex
3
@Alex GEB ist ein Buch von Douglas Hofstadter. Es ist eine Abkürzung für Gödel Escher Bach. Kurt Gödel war Mathematiker. Escher war Künstler. Bach war Pianist.
Aadit M Shah

Antworten:

201

Hier gibt es ungefähr hundert Terminologieprobleme, die sich hauptsächlich um jemanden (nicht Sie) drehen, der versucht, seine Idee wie The Best klingen zu lassen.

Alle objektorientierten Sprachen müssen mit verschiedenen Konzepten umgehen können:

  1. Kapselung von Daten zusammen mit zugehörigen Operationen an den Daten, die unter anderem als Datenelemente und Elementfunktionen oder als Daten und Methoden bezeichnet werden.
  2. Vererbung, die Fähigkeit zu sagen, dass diese Objekte genau wie die anderen Objekte sind, AUSSER für diese Änderungen
  3. Polymorphismus ("viele Formen"), bei dem ein Objekt selbst entscheidet, welche Methoden ausgeführt werden sollen, sodass Sie sich auf die Sprache verlassen können, um Ihre Anforderungen korrekt weiterzuleiten.

Nun zum Vergleich:

Das erste ist die ganze Frage "Klasse" gegen "Prototyp". Die Idee begann ursprünglich in Simula, wo mit einer klassenbasierten Methode jede Klasse eine Reihe von Objekten darstellte, die denselben Zustandsraum (lesen Sie "mögliche Werte") und dieselben Operationen gemeinsam hatten, wodurch eine Äquivalenzklasse gebildet wurde. Wenn Sie auf Smalltalk zurückblicken, da Sie eine Klasse öffnen und Methoden hinzufügen können, entspricht dies praktisch dem, was Sie in Javascript tun können.

Später wollten OO-Sprachen die statische Typprüfung verwenden können, daher kamen wir auf die Idee einer festen Klasse, die zur Kompilierungszeit festgelegt wurde. In der Open-Class-Version hatten Sie mehr Flexibilität. In der neueren Version hatten Sie die Möglichkeit, einige Arten von Korrektheit am Compiler zu überprüfen, die andernfalls getestet worden wären.

In einer "klassenbasierten" Sprache erfolgt das Kopieren zur Kompilierungszeit. In einer Prototypsprache werden die Operationen in der Prototypdatenstruktur gespeichert, die zur Laufzeit kopiert und geändert wird. Abstrakt ist eine Klasse jedoch immer noch die Äquivalenzklasse aller Objekte, die denselben Zustandsraum und dieselben Methoden verwenden. Wenn Sie dem Prototyp eine Methode hinzufügen, erstellen Sie effektiv ein Element einer neuen Äquivalenzklasse.

Warum das? in erster Linie, weil es zur Laufzeit einen einfachen, logischen und eleganten Mechanismus ergibt. Um ein neues Objekt oder eine neue Klasse zu erstellen, müssen Sie lediglich eine Tiefenkopie durchführen und alle Daten und die Prototypdatenstruktur kopieren. Vererbung und Polymorphismus erhalten Sie dann mehr oder weniger kostenlos: Die Methodensuche besteht immer darin, ein Wörterbuch nach einer Methodenimplementierung nach Namen zu fragen.

Der Grund, warum wir in Javascript / ECMA-Skripten gelandet sind, ist im Grunde, dass wir uns vor 10 Jahren mit viel weniger leistungsfähigen Computern und viel weniger ausgefeilten Browsern befasst haben. Durch die Wahl der prototypbasierten Methode konnte der Interpreter sehr einfach sein und gleichzeitig die gewünschten Eigenschaften der Objektorientierung beibehalten.

Charlie Martin
quelle
1
Richtig, liest sich dieser Paragaph so, als ob ich etwas anderes gemeint hätte? Dahl und Nyqvist haben sich "Klasse" als Sammlung von Dingen mit derselben Methodensignatur ausgedacht.
Charlie Martin
1
Sagt diese Änderung es besser?
Charlie Martin
2
Nein, sorry, CLOS stammt aus den späten 80ern. Dreamsongs.com/CLOS.html Smalltalk von 1980 en.wikipedia.org/wiki/Smalltalk und Simula mit vollständiger Objektorientierung von 1967-68 en.wikipedia.org/wiki/Simula
Charlie Martin
3
@Stephano, Sie sind nicht so unterschiedlich wie all das: Python, Ruby, Smalltalk verwenden Wörterbücher für die Methodensuche, und Javascript und Self haben Klassen. Bis zu einem gewissen Grad könnte man argumentieren, dass der Unterschied nur darin besteht, dass die prototyporientierten Sprachen ihre Implementierungen offenlegen. Es ist also wahrscheinlich gut, es nicht zu einer großen Sache zu machen: Es ist wahrscheinlich eher das Argument zwischen EMACS und vi.
Charlie Martin
21
Nützliche Antwort . +1 Weniger nützlicher Müll in den Kommentaren. Ich meine, macht es einen Unterschied, ob CLOS oder Smalltalk zuerst waren? Die meisten Leute hier sind sowieso keine Historiker.
Adam Arold
40

Ein Vergleich, der leicht auf den prototypenbasierten Ansatz ausgerichtet ist, findet sich in der Arbeit Self: The Power of Simplicity . Das Papier macht die folgenden Argumente für Prototypen:

Erstellung durch Kopieren . Das Erstellen neuer Objekte aus Prototypen erfolgt durch einen einfachen Vorgang, bei dem mit einer einfachen biologischen Metapher das Klonen kopiert wird. Das Erstellen neuer Objekte aus Klassen erfolgt durch Instanziierung, einschließlich der Interpretation von Formatinformationen in einer Klasse. Die Instanziierung ähnelt dem Bau eines Hauses nach einem Plan. Das Kopieren spricht uns als einfachere Metapher an als die Instanziierung.

Beispiele für bereits vorhandene Module . Prototypen sind konkreter als Klassen, da sie eher Beispiele für Objekte als Beschreibungen von Format und Initialisierung sind. Diese Beispiele können Benutzern helfen, Module wiederzuverwenden, indem sie sie verständlicher machen. Ein prototypbasiertes System ermöglicht es dem Benutzer, einen typischen Vertreter zu untersuchen, anstatt von ihm zu verlangen, dass er aus seiner Beschreibung einen Sinn ergibt.

Unterstützung für einzigartige Objekte . Self bietet ein Framework, das auf einzigartige Weise einzigartige Objekte mit eigenem Verhalten einbeziehen kann. Da jedes Objekt Slots benannt hat und Slots den Status oder das Verhalten enthalten können, kann jedes Objekt eindeutige Slots oder Verhaltensweisen haben. Klassenbasierte Systeme sind für Situationen konzipiert, in denen viele Objekte dasselbe Verhalten aufweisen. Es gibt keine sprachliche Unterstützung dafür, dass ein Objekt sein eigenes einzigartiges Verhalten besitzt, und es ist umständlich, eine Klasse zu erstellen, die garantiert nur eine Instanz hat [ Think Singleton Pattern ]. Das Selbst leidet unter keinem dieser Nachteile. Jedes Objekt kann mit seinem eigenen Verhalten angepasst werden. Ein eindeutiges Objekt kann das eindeutige Verhalten enthalten, und eine separate "Instanz" ist nicht erforderlich.

Beseitigung der Meta-Regression . Kein Objekt in einem klassenbasierten System kann autark sein. Ein anderes Objekt (seine Klasse) wird benötigt, um seine Struktur und sein Verhalten auszudrücken. Dies führt zu einem konzeptionell unendlichen Meta-Regress: a pointist eine Instanz der Klasse Point, die eine Instanz der Metaklasse ist Point, die eine Instanz der Metametaklasse ist Point, ad infinitum. Andererseits kann ein Objekt in prototypbasierten Systemen sein eigenes Verhalten enthalten; Es wird kein anderes Objekt benötigt, um ihm Leben einzuhauchen. Prototypen eliminieren Meta-Regression.

Self ist wahrscheinlich die erste Sprache, die Prototypen implementiert (sie hat auch andere interessante Technologien wie JIT entwickelt, die später in die JVM aufgenommen wurden). Daher sollte das Lesen der anderen Self-Artikel ebenfalls lehrreich sein.

Vijay Mathew
quelle
5
RE: Eliminierung von Meta-Regression: Im Common Lisp Object System, das klassenbasiert ist, pointist a eine Instanz der Klasse Point, eine Instanz der Metaklasse standard-class, die eine Instanz von sich selbst ist, ad finitum.
Max Nanasy
Links zu Selbstpapieren sind tot. Arbeitsverknüpfungen: Selbst: Die Kraft der Einfachheit | Eine Selbstbibliographie
user1201917
24

Sie sollten sich ein großartiges Buch über JavaScript von Douglas Crockford ansehen . Es bietet eine sehr gute Erklärung für einige der Entwurfsentscheidungen, die von JavaScript-Erstellern getroffen wurden.

Einer der wichtigen Designaspekte von JavaScript ist das prototypische Vererbungssystem. Objekte sind in JavaScript erstklassige Bürger, so dass reguläre Funktionen auch als Objekte implementiert werden (genauer gesagt 'Function'-Objekt). Meiner Meinung nach sollte es, als es ursprünglich für die Ausführung in einem Browser entwickelt wurde, zum Erstellen vieler Singleton-Objekte verwendet werden. Im Browser-DOM finden Sie in diesem Fenster, Dokument usw. alle Singleton-Objekte. Außerdem ist JavaScript eine lose typisierte dynamische Sprache (im Gegensatz zu Python, eine stark typisierte dynamische Sprache). Daher wurde ein Konzept der Objekterweiterung mithilfe der Eigenschaft 'Prototyp' implementiert.

Ich denke, es gibt einige Profis für prototypbasierte OO, wie sie in JavaScript implementiert sind:

  1. Geeignet für lose typisierte Umgebungen, ohne dass explizite Typen definiert werden müssen.
  2. Das Implementieren von Singleton-Mustern ist unglaublich einfach (vergleichen Sie JavaScript und Java in dieser Hinsicht, und Sie werden wissen, wovon ich spreche).
  3. Bietet Möglichkeiten zum Anwenden einer Methode eines Objekts im Kontext eines anderen Objekts, zum dynamischen Hinzufügen und Ersetzen von Methoden zu einem Objekt usw. (Dinge, die in stark typisierten Sprachen nicht möglich sind).

Hier sind einige der Nachteile von prototypischem OO:

  1. Keine einfache Möglichkeit, private Variablen zu implementieren. Es ist möglich, private Variablen mithilfe von Crockfords Zauberei mithilfe von Closures zu implementieren , aber es ist definitiv nicht so trivial wie die Verwendung privater Variablen in Java oder C #.
  2. Ich weiß noch nicht, wie ich mehrere Vererbungen (für was es wert ist) in JavaScript implementieren kann.
Amit
quelle
2
Verwenden Sie einfach eine Namenskonvention für private Variablen, wie es Python tut.
Aehlke
1
In js werden private Vars mit Closures ausgeführt, und dies ist unabhängig von dem von Ihnen gewählten Vererbungstyp.
Benja
6
Crockford hat viel getan, um JavaScript zu beschädigen, da eine ziemlich einfache Skriptsprache in eine masterbatorische Faszination für ihre Interna verwandelt wurde. JS hat keinen echten privaten Schlüsselwortbereich oder keine echte Mehrfachvererbung: Versuchen Sie nicht, sie zu fälschen.
Hal50000