
Die prototypenbasierte Programmierung ist ein objektorientiertes Programmierparadigma und eine Alternative zur klassenbasierten Programmierung. Prototypenbasierte Programmierung kam erstmals Mitte der 80er Jahre mit der Programmiersprache Self auf. Der populärste Vertreter dieses Paradigmas ist JavaScript.
Einige weitere prototypenbasierte Sprachen sind Lua, Io, Kevo, Rebol und Cecil.
Da mir keine offizielle Kurzschreibweise geläufig ist, verwende ich für diesen Beitrag anstelle des sperrigen Begriffes prototypenbasierte Programmierung die Abkürzung PTP.
Bauplan
Der Bauplan eines Objektes in der klassenbasierten Programmierung ist die Klasse. Sie spezifiziert, welche Attribute und Methoden ein Objekt hat. Das Objekt ist eine Instanz dieser Klasse. Von einer Klasse können beliebig viele Instanzen erzeugt werden.
In der PTP ist das Objekt der Bauplan selbst. Ein Objekt fungiert als Prototyp für andere Objekte. Neue Objekte werden durch Klonen eines Objektes erzeugt. Dabei werden alle Attribute und Methoden des Objektes kopiert.
Vererbung
Vererbung wird durch eine Verkettung von Objekten abgebildet, die sogenannte Prototype Chain. Sprachen wie Self, JavaScript und Io bringen diese Funktionalität von Haus aus mit. In JavaScript etwa wird die Prototype Chain über eine spezielle interne Property prototype abgebildet.
Erfolgt ein Zugriffsversuch auf eine Property eines Objektes, so wird dabei die Prototype Chain durchlaufen. Nehmen wir an, wir erzeugen das Objekt B als Kopie von A und das Objekt C als Kopie von B, und versuchen dann auf die Property X von C zuzugreifen – Was passiert dabei? Zunächst wird geprüft, ob C selbst eine Property X definiert hat. Ist dies der Fall, wird diese zurückgegeben, wenn nicht, wird sein Prototyp, also B, hinsichtlich dieser Property untersucht und als nächstes ggf. A. Bei statisch typisierten klassenbasierten Programmiersprachen kann diese Überprüfung zur Kompilierzeit stattfinden, in der PTP erfolgt diese Prüfung zur Laufzeit und hat bei langen Prototype Chains entsprechende Performance-Implikationen.
PTP entfernt jegliche Abstraktion. Ein Objekt ist immer sein eigener Bauplan und damit konkret, während es in der klassenbasierten Programmierung Interfaces und abstrakte Klassen gibt, die nicht selbst instanzierbar sind.
Für statisch typisierte klassenbasierte Programmiersprachen gilt ein strikter Bauplan. So behält beispielsweise in Java eine Ableitung immer die öffentlichen Properties ihrer übergeordneten Klasse. Bei Methoden muss die Signatur erhalten bleiben. Parametrisierung und Rückgabewerte dürfen sich nicht ändern, ebenso kann die Sichtbarkeit nicht uneingeschränkt geändert werden und throws-Klauseln müssen berücksichtigt werden. Für die PTP gilt das nicht. Properties eines durch Klonen entstandenen Objektes können eliminiert werden und der Prototype, also die Vererbung, kann zur Laufzeit geändert werden.
Der folgende JavaScript-Code verdeutlicht, wie eine geklonte Methode aus dem geklonten Objekt entfernt wird:
a = { foo: function () { return "bar"; } };
// gibt "bar" zurück
a.foo();
b = Object.create(a);
// gibt "bar" zurück
b.foo();
b.foo = undefined;
// gibt "{ foo: [Function: foo] }" zurück
Object.getPrototypeOf(b);
// Fehlermeldung: Uncaught TypeError: b.foo is not a function
b.foo();
Dieser JavaScript-Code ändert den Prototype eines Objektes und damit die Vererbung:
a = { foo: function () { return "bar"; } };
b = { bar: function () { return "foo"; } };
c = Object.create(a);
// gibt "bar" zurück
c.foo();
// Fehlermeldung: Uncaught TypeError: c.bar is not a function
c.bar();
Object.setPrototypeOf(c, b);
// Fehlermeldung: Uncaught TypeError: c.foo is not a function
c.foo();
// gibt "foo" zurück
c.bar();
Modifikation zur Laufzeit
Die PTP erlaubt die Modifikation von Objekten zur Laufzeit. Während sich bei statisch getypten klassenbasierten Programmiersprachen zur Laufzeit nur der State ändern kann, können in der PTP zur Laufzeit Methoden überschrieben oder gänzlich neue Properties erzeugt werden.
Wie auch in vielen klassenbasierten Sprachen existiert in der PTP üblicherweise ein Root Object, von dem sich alle erzeugten Objekte implizit ableiten. Der folgende JavaScript-Code verdeutlicht, wie zur Laufzeit das Root Object um eine Property erweitert wird und sich diese Änderung nachträglich auf die Ausprägung eines Objektes auswirkt.
a = {};
// gibt "undefined" zurück
a.foo;
Object.prototype.foo = 5;
// gibt "5" zurück
a.foo;
Klassenbasierte Programmiersprachen mit dynamischer Typisierung erlauben die Modifkation zur Laufzeit ebenso, dazu gehören unter anderem Python, Ruby, Perl, Smalltalk, Objective-C und Lisp.
Für und Wider
Die PTP hat im Wesentlichen die gleichen Vor- und Nachteile, wie die dynamische Typisierung. Ein großer Gewinn an Flexibilität geht zu Lasten der Wartbarkeit, Typsicherheit, Korrektheit bzw. Vorhersehbarkeit. Darüber hinaus kann die PTP nicht von klassenbezogenen Compiler-Optimierungen profitieren.
Hinzu kommt die Unsicherheit bei vielen Entwicklern, da sich die klassenbasierte Objektorientierung einer signifikant größeren Verbreitung erfreut. Dass auch aus diesem Grund mit ECMAScript 6 classes eingeführt wurden, ist lediglich eine Vermutung von mir. Fest steht, dass sie wohl vielen Entwickern sehr entgegenkommen dürften, die notgedrungen auch ein bisschen JavaScript machen müssen, ansonsten aber eher einen C– oder Java-Hintergrund haben. Was viele von Ihnen aber nicht wissen dürften ist, dass Klassen in JavaScript lediglich syntaktischer Zucker sind und sich unter der Haube dann doch wieder Prototypen befinden.