Inwiefern unterscheidet sich die prototypische Vererbung praktisch von der klassischen Vererbung?

27

Vererbung, Polymorphismus und Verkapselung sind die drei wichtigsten Merkmale von OOP, und von diesen hat Vererbung heutzutage eine hohe Nutzungsstatistik. Ich lerne JavaScript und hier sagen alle, dass es eine prototypische Vererbung hat, und die Leute sagen überall, dass es etwas ganz anderes ist als die klassische Vererbung.

Ich kann jedoch nicht verstehen, worin der Unterschied zum praktischen Gebrauch besteht. Mit anderen Worten, wenn Sie eine Basisklasse (einen Prototyp) definieren und dann einige Unterklassen daraus ableiten, haben Sie beide Zugriff auf Funktionalitäten Ihrer Basisklasse und können Funktionen für die abgeleiteten Klassen erweitern. Wenn wir bedenken, was ich als beabsichtigtes Ergebnis der Vererbung bezeichnete, warum sollte es uns dann etwas ausmachen, wenn wir eine prototypische oder klassische Version verwenden?

Um mich selbst klarer zu machen, sehe ich keinen Unterschied in der Nützlichkeit und den Nutzungsmustern der prototypischen und klassischen Vererbung. Dies hat zur Folge, dass ich kein Interesse daran habe zu erfahren, warum sie unterschiedlich sind, da beide dasselbe Ergebnis haben, OOAD. Wie praktisch (nicht theoretisch) unterscheidet sich die prototypische Vererbung von der klassischen Vererbung?

Saeed Neamati
quelle

Antworten:

6

Jüngster Blogeintrag über JS OO

Ich glaube, was Sie vergleichen, ist die klassische OO-Emulation in JavaScript und die klassische OO, und Sie können natürlich keinen Unterschied feststellen.

Haftungsausschluss: Ersetzen Sie alle Verweise auf "prototypal OO" durch "prototypal OO in JavaScript". Ich kenne die Besonderheiten von Self oder einer anderen Implementierung nicht.

Prototyp OO ist jedoch anders. Mit Prototypen haben Sie nur Objekte und können Objekte nur in die Prototypenkette anderer Objekte einfügen. Wann immer Sie auf eine Eigenschaft eines Objekts zugreifen, durchsuchen Sie dieses Objekt und alle Objekte in der Prototypkette.

Für prototypische OO gibt es keine Vorstellung von Einkapselung. Die Kapselung ist ein Merkmal von Umfang, Verschlüssen und erstklassigen Funktionen, hat jedoch nichts mit prototypischem OO zu tun. Es gibt auch keine Vorstellung von Vererbung, was die Leute "Vererbung" nennen, ist eigentlich nur Polymorphismus.

Es hat jedoch Polymorphismus.

Beispielsweise

var Dog = {
  walk: function() { console.log("walks"); }
}

var d = Object.create(Dog);
d.walk();

Hat eindeutig dZugriff auf die Methode Dog.walkund diese weist Polymorphismus auf.

Tatsächlich gibt es also einen großen Unterschied . Sie haben nur Polymorphismus.

Wie bereits erwähnt, können Sie auf Wunsch (ich habe keine Ahnung, warum Sie dies tun würden) klassisches OO in JavaScript emulieren und haben Zugriff auf (eingeschränkte) Kapselung und Vererbung.

Raynos
quelle
1
@Rayons, Google für die prototypische Vererbung und Sie werden sehen, dass die prototypische Vererbung erfolgt. Auch von Yahoo!
Saeed Neamati
@ Saeed-Vererbung ist ein vager Begriff und wird häufig missbraucht. Was sie mit "Vererbung" meinen, bezeichne ich als "Polymorphismus". Die Definitionen sind zu vage.
Raynos
Nein @Rayons, ich meinte, das Wort prototypisch ist der richtige Begriff, nicht prototypisch . Ich habe nicht über Vererbung gesprochen :)
Saeed Neamati
1
@Saeed, das ist einer dieser Tippfehler, die mir gerade durch den Kopf gehen.
Darauf habe
1
Hmm .. Terminologie. ick. Ich würde die Art und Weise, wie JavaScript-Objekte mit ihren Prototypen in Beziehung stehen, als "Delegation" bezeichnen. Der Polymorphismus scheint mir ein untergeordnetes Problem zu sein, da ein gegebener Name für verschiedene Objekte auf unterschiedlichen Code verweisen kann.
Sean McMillan
15

Die klassische Vererbung erbt das Verhalten ohne Status von der übergeordneten Klasse. Es erbt das Verhalten in dem Moment, in dem das Objekt instanziiert wird.

Die prototypische Vererbung erbt das Verhalten und den Status des übergeordneten Objekts. Es erbt das Verhalten und den Zustand zum Zeitpunkt des Objektaufrufs. Wenn sich das übergeordnete Objekt zur Laufzeit ändert, sind der Status und das Verhalten der untergeordneten Objekte betroffen.

Der "Vorteil" der prototypischen Vererbung besteht darin, dass Sie den Status und das Verhalten "patchen" können, nachdem alle Ihre Objekte instanziiert wurden. Beispielsweise werden im Ext JS-Framework häufig "Überschreibungen" geladen, die die Kernkomponenten des Frameworks nach der Instanziierung des Frameworks patchen.

Joeri Sebrechts
quelle
4
In der Praxis bereiten Sie sich auf eine Welt voller Verletzungen vor, wenn Sie den Staat erben. Der geerbte Zustand wird freigegeben, wenn er sich auf einem anderen Objekt befindet.
Sean McMillan
1
Mir fällt ein, dass es in Javascript wirklich keinen vom Staat getrennten Begriff von Verhalten gibt. Abgesehen von der Konstruktorfunktion kann jedes Verhalten wie jeder andere Zustand nach der Objekterstellung zugewiesen / geändert werden.
Joeri Sebrechts
Python hat also keine klassische Vererbung? Wenn ich habe class C(object): def m(self, x): return x*2und dann, instance = C()wenn ich renne, instance.m(3)bekomme ich 6. Aber wenn ich mich dann Cso ändere C.m = lambda s, x: x*xund ich renne instance.m(3)bekomme ich jetzt 9. Das gleiche gilt, wenn ich class D(C)eine Methode erstelle und ändere, Cdann Dempfangen auch alle Instanzen der geänderten Methode. Verstehe ich das falsch oder bedeutet das, dass Python keine klassische Vererbung gemäß Ihrer Definition hat?
mVChr
@mVChr: ​​Sie erben immer noch Verhalten und geben in diesem Fall nicht an, was auf klassische Vererbung hinweist, aber es deutet auf ein Problem mit meiner Definition von "erbt das Verhalten in dem Moment, in dem das Objekt instanziiert wird" hin. Ich bin nicht ganz sicher, wie ich die Definition korrigieren soll.
Joeri Sebrechts
9

Erstens: Meistens werden Sie Objekte verwenden, nicht definieren, und die Verwendung von Objekten ist unter beiden Paradigmen gleich.

Zweitens: Die meisten prototypischen Umgebungen verwenden dieselbe Art der Unterteilung wie klassenbasierte Umgebungen - veränderbare Daten auf der Instanz mit vererbten Methoden. Es gibt also wieder sehr wenig Unterschied. (Siehe meine Antwort auf diese Stapelüberlauffrage und die Selbstorganisationsprogramme ohne Klassen . Eine PDF-Version finden Sie bei Citeseer .)

Drittens: Die Dynamik von Javascript hat einen viel größeren Einfluss als die Art der Vererbung. Die Tatsache, dass ich allen Instanzen eines Typs eine neue Methode hinzufügen kann, indem ich sie dem Basisobjekt zuordnete, ist ordentlich, aber ich kann dasselbe in Ruby tun, indem ich die Klasse erneut öffne.

Viertens: Die praktischen Unterschiede sind gering, während das praktische Problem des Vergessens der Benutzung newviel größer ist - das heißt, Sie sind viel wahrscheinlicher davon betroffen, wenn Sie ein vergessen habennew als Sie vom Unterschied zwischen prototypischem und klassischem Code betroffen zu sein .

Der praktische Unterschied zwischen prototypischer und klassischer Vererbung besteht jedoch darin, dass Ihre Methoden (Klassen) für das Halten von Dingen mit den Daten (Instanzen) für das Halten von Dingen identisch sind. Dies bedeutet, dass Sie Ihre Klassen aufbauen können Stück für Stück mit den gleichen Werkzeugen zur Objektbearbeitung, die Sie für jede Instanz verwenden würden. (Dies ist in der Tat die Vorgehensweise aller Klassenemulationsbibliotheken . Schauen Sie sich Traits.js an, um einen Ansatz zu finden, der nicht allen anderen sehr ähnlich ist. ) Dies ist vor allem dann interessant, wenn Sie eine Metaprogrammierung durchführen.

Sean McMillan
quelle
yay, beste Antwort IMO!
Aivar
0

Die prototypische Vererbung in JavaScript unterscheidet sich in folgenden wichtigen Punkten von Klassen:

Konstruktoren sind einfach Funktionen, die Sie aufrufen können, ohne new:

function Circle (r, x, y) { 
  //stuff here
}
Var c = new Circle();
Circle.call(c, x, y, z); //This works and you can do it over and over again.

Es gibt keine privaten Variablen oder Methoden, bestenfalls können Sie dies tun:

function Circle (r, x, y) {
  var color = 'red';
  function drawCircle () {
    //some code here with x, y and r
  }
  drawCircle();
  this.setX = function (x_) {
    x = x_;
    drawCircle();
  }

}
Circle.prototype.getX = function () {
   //Can't access x!
}

Im vorherigen Beispiel können Sie die Klasse nicht sinnvoll erweitern, wenn Sie auf gefälschte private Variablen und Methoden zurückgreifen. Außerdem werden alle von Ihnen deklarierten öffentlichen Methoden jedes Mal neu erstellt, wenn eine neue Instanz erstellt wird.

Björn
quelle
Sie können die "Klasse" sinnvoll erweitern. Sie können nicht nur die lokalen Variablen zugreifen color, r, x, yund drawCircle, die den lexikalischen Geltungsbereich gebunden sindCircle
Raynos
Es ist wahr, Sie können es, aber Sie können es nicht tun, ohne eine Reihe von 'öffentlichen' Methoden zu erstellen, die dem Kontext hinzugefügt werden und jedes Mal erstellt werden, wenn Sie eine Instanz erstellen.
Bjorn
Dies ist ein sehr seltsames Muster, das Sie dort verwenden. Circle.call()als Konstrukteur? Es sieht so aus, als ob Sie versuchen, "funktionale Objekte" zu beschreiben (eine falsche Bezeichnung ...)
Sean McMillan
Das würde ich nie tun, ich sage nur, dass es möglich ist, den Konstruktor mit JavaScript immer wieder aufzurufen, aber nicht in Sprachen mit traditionellen Klassen.
Bjorn
1
Aber wie wichtig sind diese Unterschiede? Ich sehe es nicht als wichtig an, Objekte durch Aufrufen einer Funktion zu konstruieren, ohne 'new' zu verwenden, sondern nur einen kleinen Syntaxunterschied. Ebenso ist es nicht grundlegend anders, keine privaten Variablen zu haben, sondern nur ein kleiner Unterschied. Plus, Sie können (etwas ähnliches) private Variablen haben, in der Art und Weise Sie beschreiben.
Roel