Was sind die Vorteile von prototypbasiertem OOP gegenüber klassenbasiertem OOP?


47

Als ich anfing, Javascript zu programmieren, nachdem ich mich hauptsächlich mit OOP in klassenbasierten Sprachen befasst hatte, war ich verwirrt darüber, warum prototypbasiertes OOP jemals dem klassenbasierten OOP vorgezogen würde.

  1. Welche strukturellen Vorteile bietet die Verwendung von prototypbasiertem OOP, falls vorhanden? (Würden wir erwarten, dass es in bestimmten Anwendungen schneller oder weniger speicherintensiv ist?)
  2. Was sind die Vorteile aus der Sicht eines Programmierers? (Ist es beispielsweise einfacher, bestimmte Anwendungen zu codieren oder den Code anderer mithilfe von Prototypen zu erweitern?)

Bitte betrachten Sie diese Frage nicht als eine Frage zu Javascript (das im Laufe der Jahre viele Fehler hatte, die nichts mit dem Prototyping zu tun haben). Betrachten Sie es stattdessen im Kontext der theoretischen Vorteile von Prototyping gegenüber Klassen.

Danke.


Antworten:


46

Ich hatte ziemlich viel Erfahrung mit beiden Ansätzen, als ich ein RPG-Spiel in Java schrieb. Ursprünglich habe ich das ganze Spiel mit klassenbasiertem OOP geschrieben, aber irgendwann wurde mir klar, dass dies der falsche Ansatz war (er wurde mit der Erweiterung der Klassenhierarchie nicht mehr aufrechtzuerhalten). Ich habe daher die gesamte Codebasis in prototypbasierten Code konvertiert. Das Ergebnis war viel besser und einfacher zu handhaben.

Quellcode hier, wenn Sie interessiert sind ( Tyrant - Java Roguelike )

Hier sind die Hauptvorteile:

  • Es ist einfach , neue "Klassen" zu erstellen - kopieren Sie einfach den Prototyp und ändern Sie ein paar Eigenschaften und voila ... neue Klasse. Ich habe dies verwendet, um eine neue Art von Trank zu definieren, zum Beispiel in jeweils 3-6 Zeilen Java. Viel besser als eine neue Klassendatei und jede Menge Boilerplate!
  • Es ist möglich, mit vergleichsweise wenig Code eine extrem große Anzahl von "Klassen" zu erstellen und zu verwalten - Tyrant hatte beispielsweise etwa 3000 verschiedene Prototypen mit insgesamt nur etwa 42.000 Codezeilen. Das ist ziemlich erstaunlich für Java!
  • Mehrfachvererbung ist einfach - Sie kopieren einfach eine Teilmenge der Eigenschaften eines Prototyps und fügen sie über den Eigenschaften eines anderen Prototyps ein. In einem Rollenspiel möchten Sie vielleicht, dass ein "Stahlgolem" einige der Eigenschaften eines "Stahlobjekts" und einige der Eigenschaften eines "Golems" und einige der Eigenschaften eines "unintelligenten Monsters" hat. Einfach mit Prototypen, versuchen Sie das mit einer Erbschaftserbschaft ......
  • Mit Eigenschaftsmodifikatoren können Sie clevere Dinge tun. Wenn Sie die generische Methode "read property" mit cleverer Logik versehen, können Sie verschiedene Modifikatoren implementieren. Zum Beispiel war es einfach, einen magischen Ring zu definieren, der demjenigen, der ihn trug, +2 Stärke hinzufügte. Die Logik dafür lag im Ringobjekt, nicht in der Methode "Lesestärke". Sie mussten also nicht viele Bedingungsprüfungen an anderer Stelle in Ihrer Codebasis durchführen (z. B. "Trägt der Charakter einen Ring mit erhöhter Stärke?").
  • Instanzen können zu Vorlagen für andere Instanzen werden. Wenn Sie beispielsweise ein Objekt "klonen" möchten, können Sie einfach das vorhandene Objekt als Prototyp für das neue Objekt verwenden. Keine Notwendigkeit, viele komplexe Klonlogiken für verschiedene Klassen zu schreiben.
  • Es ist ziemlich einfach, das Verhalten zur Laufzeit zu ändern - dh Sie können Eigenschaften ändern und ein Objekt zur Laufzeit so ziemlich willkürlich "morphen". Ermöglicht coole In-Game-Effekte, und wenn Sie dies mit einer "Skriptsprache" verbinden, ist zur Laufzeit so ziemlich alles möglich.
  • Es ist eher für einen "funktionalen" Programmierstil geeignet - Sie schreiben in der Regel viele Funktionen, mit denen Objekte und Aktionen angemessen analysiert werden, anstatt Logik in Methoden einzubetten, die an bestimmte Klassen gebunden sind. Ich persönlich bevorzuge diesen FP-Stil.

Hier sind die Hauptnachteile:

  • Sie verlieren die Sicherheit der statischen Typisierung, da Sie effektiv ein dynamisches Objektsystem erstellen. Dies bedeutet in der Regel, dass Sie weitere Tests schreiben müssen, um sicherzustellen, dass das Verhalten korrekt ist und die Objekte von der richtigen "Art" sind.
  • Es entsteht ein gewisser Performance-Overhead. Da das Lesen von Objekteigenschaften im Allgemeinen eine oder mehrere Map-Lookups durchlaufen muss, sind die Performancekosten gering. In meinem Fall war es kein Problem, aber es könnte in einigen Fällen ein Problem sein (z. B. ein 3D-FPS mit vielen Objekten, die in jedem Frame abgefragt werden)
  • Refactorings funktionieren nicht auf die gleiche Weise - in einem prototypbasierten System "bauen" Sie im Wesentlichen Ihre Erbschaftshierarchie mit Code auf. IDEs / Refactoring-Tools können Ihnen nicht wirklich helfen, da sie Ihren Ansatz nicht verfälschen können. Ich fand das nie ein Problem, aber es könnte außer Kontrolle geraten, wenn Sie nicht vorsichtig sind. Sie möchten wahrscheinlich Tests, um zu überprüfen, ob Ihre Vererbungshierarchie korrekt aufgebaut ist!
  • Es ist ein bisschen fremd - Leute, die an einen konventionellen OOP-Stil gewöhnt sind, können leicht verwirrt werden. "Was meinst du damit, dass es nur eine Klasse namens" Thing "gibt?!?" - "Wie verlängere ich diese letzte Klasse!?!" - "Sie verletzen die OOP-Prinzipien !!!" - "Es ist falsch, all diese statischen Funktionen zu haben, die auf irgendeine Art von Objekt wirken!?!?"

Zum Schluss einige Implementierungshinweise:

  • Ich habe eine Java-HashMap für Eigenschaften und einen übergeordneten Zeiger für den Prototyp verwendet. Dies funktionierte einwandfrei, hatte jedoch die folgenden Nachteile: a) Eigenschaftslesevorgänge mussten manchmal über eine lange übergeordnete Kette zurückverfolgt werden, was die Leistung beeinträchtigte. Dies kann subtile Fehler verursachen, wenn Sie nicht vorsichtig sind!
  • Wenn ich das noch einmal tun würde, würde ich eine unveränderliche persistente Karte für die Eigenschaften (ähnlich wie Clojures persistente Karten ) oder meine eigene Implementierung einer persistenten Java-Hash-Karte verwenden . Dann würden Sie den Vorteil eines billigen Kopierens / Änderns in Verbindung mit unveränderlichem Verhalten erhalten und Sie müssten Objekte nicht dauerhaft mit ihren Eltern verknüpfen.
  • Sie können Spaß haben, wenn Sie Funktionen / Methoden in Objekteigenschaften einbetten. Der von mir in Java verwendete Hack (anonyme Untertypen einer "Script" -Klasse) war nicht sehr elegant. Wenn ich das noch einmal mache, würde ich wahrscheinlich eine leicht einbettbare Sprache für Skripte verwenden (Clojure oder Groovy).

5

(+1) Es ist eine gute Analyse. Obwohl es mehr auf dem Java-Modell basiert, hat beispielsweise Delphi, C #, VB.Net explizite Eigenschaften.
umlcat

3
@umlcat - Ich bin der Meinung, dass das Java-Modell dem Delphi / C # -Modell ziemlich ähnlich ist (abgesehen vom netten syntaktischen Zucker für den Eigenschaftenzugriff) - Sie müssen die gewünschten Eigenschaften in Ihrer Klassendefinition noch statisch deklarieren. Der Sinn eines Prototyp-Modells ist, dass diese Definition nicht statisch ist und Sie keine Erklärungen im Voraus
abgeben müssen

Das hat einen großen verfehlt. Sie können den Prototyp ändern, wodurch die Eigenschaften für jede einzelne Instanz geändert werden, auch nachdem sie erstellt wurden, ohne den Konstruktor des Prototyps zu berühren.
Erik Reppen

Da die Mehrfachvererbung einfacher ist, haben Sie sie mit Java verglichen, das die Mehrfachvererbung nicht unterstützt. Ist sie jedoch einfacher im Vergleich zu unterstützten Sprachen wie C ++?
Piovezan

2

Der Hauptvorteil von prototypbasiertem OOP ist, dass Objekte und "Klassen" zur Laufzeit erweitert werden können.

In Class-based OOP gibt es mehrere gute Funktionen, die leider von der Programmiersprache abhängen.

Object Pascal (Delphi), VB.Net & C #, bietet eine sehr direkte Möglichkeit, Eigenschaften (nicht zu verwechseln mit Feldern) und Zugriffsmethoden für Eigenschaften zu verwenden, während auf Java & C ++ - Eigenschaften über Methoden zugegriffen wird. Und PHP hat eine Mischung aus beidem, die man "magische Methoden" nennt.

Es gibt einige dynamische Typisierungsklassen, obwohl die OO-Hauptsprachen eine statische Typisierung aufweisen. Ich halte statisches Tippen mit Class OO für sehr nützlich, da es eine Funktion namens Object Introspection ermöglicht, mit der diese IDE erstellt und Webseiten visuell und schnell entwickelt werden können.


0

Ich muss @umlcat zustimmen. Klassenerweiterung ist ein großer Vorteil. Angenommen, Sie möchten einer Zeichenfolgenklasse über einen längeren Zeitraum mehr Funktionen hinzufügen. In C ++ würden Sie dies durch die fortgesetzte Vererbung früherer Generationen von Zeichenfolgenklassen tun. Das Problem bei diesem Ansatz ist, dass jede Generation im Wesentlichen zu einem eigenen Typ wird, was zu einem massiven Umschreiben vorhandener Codebasen führen kann. Mit der prototypischen Vererbung fügen Sie der ursprünglichen Basisklasse einfach eine neue Methode hinzu, ohne dass überall ererbte Klassen und Vererbungsbeziehungen massiv aufgebaut werden müssen. Ich würde gerne sehen, dass C ++ einen ähnlichen Erweiterungsmechanismus in seinem neuen Standard hat. Aber ihr Komitee scheint von Leuten geleitet zu werden, die die eingängigen und beliebten Features hinzufügen wollen.


1
Vielleicht möchten Sie Monolithen "Unstrung" lesen , hat std::stringbereits zu viele Mitglieder, die unabhängige Algorithmen oder zumindest Nicht-Freunde-Nicht-Mitglieder sein sollten. Außerdem können neue Member-Funktionen hinzugefügt werden, ohne das In-Memory-Layout zu ändern, sofern Sie die ursprüngliche Klasse ändern können.
Deduplikator
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.