Weiterführende Themen

Geschichte der OOP-Unterstützung durch ActionScript

Da ActionScript 3.0 auf früheren Versionen von ActionScript aufbaut, sind Kenntnisse, wie sich das ActionScript-Objektmodell entwickelt hat, durchaus von Nutzen. ActionScript begann als einfacher Mechanismus zur Skripterstellung für frühe Versionen von Flash Professional. Später begannen Programmierer, mit ActionScript zunehmend komplexere Anwendungen zu erstellen. Um den Anforderungen dieser Programmierer zu entsprechen, wurden jeder neuen Version weitere Sprachfunktionen hinzugefügt, welche die Erstellung komplexer Anwendungen vereinfachten.

ActionScript 1.0

ActionScript 1.0 ist die Version der Sprache, die in Flash Player 6 und früheren Versionen verwendet wurde. Bereits in diesem frühen Entwicklungsstadium basierte das ActionScript-Objektmodell auf dem Konzept eines Objekts als grundlegendem Datentyp. Ein ActionScript-Objekt ist ein zusammengesetzter Datentyp mit einer Reihe von Eigenschaften. Im Sinne eines Objektmodells umfasst der Begriff Eigenschaften alle Elemente, die an ein Objekt angefügt werden können, beispielsweise Variable, Funktionen oder Methoden.

Obwohl diese erste Generation von ActionScript das Definieren von Klassen mit dem Schlüsselwort class noch nicht unterstützte, konnten Sie eine Klasse mit einem speziellen Objekttyp namens „Prototypobjekt“ definieren. Anstatt das Schlüsselwort class zum Erstellen einer abstrakten Klassendefinition zu verwenden, die Sie in konkrete Objekte instanziieren (wie in klassenbasierten Sprachen wie Java und C++), verwenden Sie in prototypbasierten Sprachen wie ActionScript 1.0 ein existierendes Objekt als Modell (oder Prototyp) für andere Objekte. Während Objekte in einer klassenbasierten Sprache auf eine Klasse verweisen können, die als Vorlage dient, verweisen Objekte in einer prototypbasierten Sprache stattdessen auf ein anderes Objekt (ihren Prototyp), der als Vorlage dient.

Zum Erstellen einer Klasse in ActionScript 1.0 definieren Sie eine Konstruktorfunktion für diese Klasse. In ActionScript sind Funktionen tatsächliche Objekte, nicht nur abstrakte Definitionen. Die von Ihnen erstellte Konstruktorfunktion dient als prototypisches Objekt für Instanzen dieser Klasse. Im folgenden Codebeispiel werden eine Klasse namens „Shape“ erstellt und eine Eigenschaft mit der Bezeichnung visible definiert, die standardmäßig auf true gesetzt ist:

// base class 
function Shape() {} 
// Create a property named visible. 
Shape.prototype.visible = true;

Diese Konstruktorfunktion definiert eine Shape-Klasse, die Sie wie folgt mit dem new-Operator instanziieren können:

myShape = new Shape();

Das Shape()-Konstruktorfunktionsobjekt kann nicht nur als Prototyp für Instanzen der Shape-Klasse dienen, sondern auch als Prototyp für Unterklassen der Shape-Klasse verwendet werden, d. h. für Klassen, die die Shape-Klasse erweitern.

Das Erstellen einer Klasse, bei der es sich um eine Unterklasse der Shape-Klasse handelt, umfasst zwei Schritte. Als Erstes erstellen Sie die Klasse, indem Sie eine Konstruktorfunktion für die Klasse definieren. Dies wird im folgenden Code gezeigt:

// child class 
function Circle(id, radius) 
{ 
this.id = id; 
this.radius = radius; 
}

Dann verwenden Sie den new-Operator, um die Shape-Klasse zu deklarieren, die der Prototyp für die Circle-Klasse ist. In der Standardeinstellung verwendet jede von Ihnen erstellte Klasse die Object-Klasse als Prototyp. Das bedeutet, dass Circle.prototype derzeit ein generisches Objekt enthält (eine Instanz der Object-Klasse). Um festzulegen, dass der Circle-Prototyp jetzt „Shape“ anstelle von „Object“ sein soll, verwenden Sie den folgenden Code. Darin wird der Wert von Circle.prototype so geändert, dass er ein Shape-Objekt anstelle eines generischen Objekts enthält:

// Make Circle a subclass of Shape. 
Circle.prototype = new Shape();

Die Shape-Klasse und die Circle-Klasse sind jetzt in einer Vererbungsbeziehung miteinander verknüpft, die allgemein als Prototypkette bezeichnet wird. Im folgenden Diagramm werden die Beziehungen in einer Prototypkette verdeutlicht:

Die Basisklasse am Ende jeder Prototypkette ist die Object-Klasse. Die Object-Klasse enthält eine statische Eigenschaft namens Object.prototype, die auf das Basis-Prototypobjekt für alle in ActionScript 1.0 erstellten Objekte zeigt. Das nächste Objekt in der Beispiel-Prototypkette ist das Shape-Objekt. Der Grund dafür ist, dass die Shape.prototype-Eigenschaft nie explizit eingestellt wurde, daher enthält es noch immer ein generisches Objekt (eine Instanz der Object-Klasse). Das abschließende Glied in dieser Kette ist die Circle-Klasse, die mit ihrem Prototyp verknüpft ist (die Circle.prototype-Eigenschaft enthält ein Shape-Objekt).

Wenn Sie wie im folgenden Beispielcode eine Instanz der Circle-Klasse erstellen, so erbt die Instanz die Prototypkette der Circle-Klasse:

// Create an instance of the Circle class. 
myCircle = new Circle();

Das Beispiel enthielt eine Eigenschaft namens visible als Mitglied der Shape-Klasse. In diesem Beispiel existiert die visible-Eigenschaft nicht als Teil des myCircle-Objekts, sondern als Mitglied des Shape-Objekts. Die folgende Codezeile gibt true zurück:

trace(myCircle.visible); // output: true

Die Laufzeit kann sicherstellen, dass das myCircle-Objekt die visible-Eigenschaft erbt, indem es die Prototypkette aufwärts durchläuft. Bei Ausführung dieses Codes sucht die Laufzeit zunächst in den Eigenschaften des myCircle-Objekts nach einer Eigenschaft namens visible, findet sie jedoch nicht. Als Nächstes sucht sie im Circle.prototype-Objekt, findet jedoch noch immer keine Eigenschaft namens visible. Auf dem weiteren Weg durch die Prototypkette findet die Laufzeit schließlich die im Shape.prototype-Objekt definierte Eigenschaft visible und gibt den Wert dieser Eigenschaft zurück.

Der Einfachheit halber werden viele Details und Feinheiten der Prototypkette weggelassen. Stattdessen sollen ausreichend Informationen zur Verfügung gestellt werden, um ein Verständnis des Objektmodells von ActionScript 3.0 zu ermöglichen.

ActionScript 2.0

Mit ActionScript 2.0 wurden neue Schlüsselwörter wie class, extends, public und private eingeführt, mit denen Klassen so definiert werden konnten, wie es Programmierer mit Kenntnissen von klassenbasierten Sprachen wie Java und C++ gewohnt sind. Es ist wichtig zu verstehen, dass sich der zugrunde liegende Vererbungsmechanismus zwischen ActionScript 1.0 und ActionScript 2.0 nicht geändert hat. In ActionScript 2.0 wurde lediglich eine neue Syntax für die Definition von Klassen hinzugefügt. Die Prototypkette ist in beiden Versionen der Programmiersprache identisch.

Mit der in ActionScript 2.0 neu eingeführten Syntax, die in dem folgenden Codeauszug dargestellt wird, können Klassen intuitiver definiert werden, wie viele Programmierer bestätigen:

// base class 
class Shape 
{ 
var visible:Boolean = true; 
}

Mit ActionScript 2.0 wurden auch Typanmerkungen für die Typüberprüfung während der Kompilierung eingeführt. Mit diesen Typanmerkungen können Sie deklarieren, dass die Eigenschaft visible aus dem vorangegangenen Beispiel nur einen booleschen Wert enthalten darf. Das neue Schlüsselwort extends vereinfacht darüber hinaus das Erstellen einer Unterklasse. Im folgenden Beispielcode wird ein Prozess, für den in ActionScript 1.0 zwei Schritte erforderlich waren, mithilfe des Schlüsselworts extends in nur einem Schritt abgeschlossen:

// child class 
class Circle extends Shape 
{ 
    var id:Number; 
    var radius:Number; 
    function Circle(id, radius) 
    { 
        this.id = id; 
        this.radius = radius; 
        } 
}

Der Konstruktor wird nun als Teil der Klassendefinition deklariert und die Klasseneigenschaften id und radius müssen ebenfalls explizit deklariert werden.

Darüber hinaus wurde in ActionScript 2.0 Unterstützung für die Definition von Schnittstellen eingefügt. Somit können Sie objektorientierte Programme mit formal definierten Protokollen für die Kommunikation von Objekten untereinander aufwerten.

ActionScript 3.0-Klassenobjekt

Ein allgemeines Schema bei der objektorientierten Programmierung, das im Wesentlichen mit Java und C++ in Verbindung gebracht wird, verwendet Klassen zur Definition der Objekttypen. Programmiersprachen, die dieses Schema angenommen haben, neigen dazu, Klassen zum Konstruieren von Instanzen des von der Klasse definierten Datentyps zu verwenden. ActionScript verwendet für beides Klassen, der Ursprung als eine prototypbasierte Sprache fügt jedoch eine weitere interessante Eigenschaft hinzu. ActionScript erstellt für jede Klassendefinition ein spezielles Klassenobjekt, dass die gemeinsame Nutzung von Verhalten und Zustand ermöglicht. Für viele ActionScript-Programmierer hat diese Unterscheidung keine praktischen Auswirkungen. ActionScript 3.0 ist so konzipiert, dass Sie moderne objektorientierte ActionScript-Anwendungen erstellen können, ohne dass Sie diese speziellen Klassenobjekte verwenden oder gar verstehen müssen.

Das folgende Diagramm zeigt die Struktur eines Klassenobjekts, das eine einfache Klasse namens „A“ darstellt, die mit der Anweisung class A {} definiert wurde:

Jedes Rechteck im Diagramm stellt ein Objekt dar. Jedes Objekt im Diagramm hat einen tiefgestellten Buchstaben A, der kennzeichnet, dass es zur Klasse „A“ gehört. Das Klassenobjekt (CA) enthält Verweise auf verschiedene andere wichtige Objekte. Ein Instanz-Traitsobjekt (TA) speichert die Instanzeigenschaften, die in einer Klassendefinition definiert sind. Ein Klassen-Traitsobjekt (TCA) stellt den internen Typ der Klasse dar und speichert die statischen Eigenschaften, die von der Klasse definiert wurden (das tiefgestellte Zeichen C steht für „Class“ [Klasse]). Das Prototypobjekt (PA) verweist immer auf das Klassenobjekt, an das es ursprünglich über die constructor-Eigenschaft angefügt wurde.

Traitsobjekt

Das in ActionScript 3.0 neu eingeführte Traitsobjekt wurde zur Performanceverbesserung implementiert. In früheren Versionen von ActionScript konnte das Nachschlagen eines Namens ein sehr zeitaufwändiger Prozess sein, da Flash Player die Prototypkette aufwärts durchlief. In ActionScript 3.0 wird die Suche eines Namens sehr viel effizienter und weniger zeitaufwändig durchgeführt, da geerbte Eigenschaften aus den übergeordneten Klassen in die Traitsobjekte der Unterklassen kopiert werden.

Ein direkter Zugriff auf das Traitsobjekt über den Programmcode ist nicht möglich, aber das Vorhandensein ist aufgrund der Performanceverbesserungen und besseren Speichernutzung deutlich spürbar. Das Traitsobjekt stellt der AVM2 ausführliche Informationen zu Layout und Inhalt einer Klasse zur Verfügung. Mit diesen Daten ist die AVM2 in der Lage, die Ausführungszeit deutlich zu reduzieren, da sie häufig direkte Maschinenanweisungen erzeugen kann, um direkt ohne zeitaufwändiges Suchen von Namen auf Eigenschaften oder Aufrufmethoden zuzugreifen.

Dank des Traitsobjekts belegt ein Objekt deutlich weniger Speicherplatz als ein ähnliches Objekt in früheren Versionen von ActionScript. Ist eine Klasse versiegelt (also nicht dynamisch deklariert), benötigt eine Klasseninstanz keine Hashtabelle für dynamisch hinzugefügte Eigenschaften und enthält nur wenig mehr als einen Zeiger auf die Traitsobjekte und einige Slots für feste Eigenschaften, die in der Klasse definiert sind. Daher belegt ein Objekt, das in ActionScript 2.0 noch 100 Byte im Arbeitsspeicher belegte, in ActionScript 3.0 nur noch 20 Byte.

Hinweis: Das Traitsobjekt ist ein internes Implementationsdetail. Es besteht keine Garantie, dass es in zukünftigen Versionen von ActionScript unverändert bleibt oder nicht wieder verschwindet.

Prototypobjekt

Jedes ActionScript-Klassenobjekt verfügt über eine Eigenschaft namens prototype, die einen Verweis auf das Prototypobjekt der Klasse darstellt. Das Prototypobjekt ist ein Vermächtnis aus den Ursprüngen von ActionScript als eine prototypbasierte Sprache. Weitere Informationen finden Sie unter Geschichte der OOP-Unterstützung durch ActionScript.

Die prototype-Eigenschaft ist schreibgeschützt, d. h. sie kann nicht geändert werden, um auf andere Objekte zu verweisen. Dies unterscheidet sich von der prototype-Eigenschaft einer Klasse in früheren Versionen von ActionScript. Hier konnte der Prototyp neu zugewiesen werden, sodass er auf eine andere Klasse verwies. Auch wenn die Eigenschaft prototype schreibgeschützt ist, das Prototypobjekt, auf das sie verweist, ist es nicht. Anders ausgedrückt, einem Prototypobjekt können neue Eigenschaften hinzugefügt werden. Einem Prototypobjekt hinzugefügte Eigenschaften stehen allen Instanzen der Klasse zur Verfügung.

Die Prototypkette, die in früheren Versionen von ActionScript den einzigen Vererbungsmechanismus darstellte, spielt in ActionScript 3.0 nur noch eine sekundäre Rolle. Hier ist der primäre Vererbungsmechanismus die Vererbung fester Eigenschaften, die intern vom Traitsobjekt verarbeitet wird. Eine feste Eigenschaft ist eine Variable oder eine Methode, die als Teil einer Klassendefinition definiert ist. Die Vererbung von festen Eigenschaften wird auch als Klassenvererbung bezeichnet, da sie der einzige Vererbungsmechanismus ist, der mit Schlüsselwörtern wie class, extends und override verbunden ist.

Die Vererbungskette bietet einen alternativen Vererbungsmechanismus, der dynamischer als die Vererbung fester Eigenschaften ist. Sie können dem Prototypobjekt einer Klasse Eigenschaften nicht nur als Teil der Klassendefinition hinzufügen, sondern über die Eigenschaft prototype des Klassenobjekts auch zur Laufzeit. Beachten Sie jedoch Folgendes: wenn Sie den Compiler auf den strikten Modus einstellen, können Sie nicht auf die dem Prototypobjekt hinzugefügten Eigenschaften zugreifen, es sei denn, Sie deklarieren eine Klasse mit dem Schlüsselwort dynamic.

Ein gutes Beispiel für eine Klasse, bei der mehrere Eigenschaften an das Prototypobjekt angehängt wurden, ist die Object-Klasse. Bei den Methoden toString() und valueOf() der Objektklasse handelt es sich tatsächlich um Funktionen, die den Eigenschaften des Prototypobjekts der Object-Klasse zugeordnet sind. Der folgende Code zeigt, wie die Deklaration dieser Methoden theoretisch aussehen könnte (die tatsächliche Implementation unterscheidet sich aufgrund der Implementationsdetails ein wenig):

public dynamic class Object 
{ 
    prototype.toString = function() 
    { 
        // statements 
    }; 
    prototype.valueOf = function() 
    { 
        // statements 
    }; 
}

Wie bereits erwähnt, können Sie eine Eigenschaft an das Prototypobjekt einer Klasse außerhalb der Klassendefinition anhängen. Beispielsweise kann die Methode toString() auch außerhalb der Object-Klassendefinition definiert werden. Dies wird im folgenden Beispiel gezeigt:

Object.prototype.toString = function() 
{ 
    // statements 
};

Im Gegensatz zur Vererbung von festen Eigenschaften ist für die Prototypvererbung kein Schlüsselwort override erforderlich, wenn Sie eine Methode in einer Unterklasse neu definieren möchten. Wenn Sie die Methode valueOf() in einer Unterklasse der Object-Klasse neu definieren möchten, haben Sie drei Optionen: Zunächst können Sie eine valueOf()-Methode für das Prototypobjekt der Unterklasse in der Klassendefinition definieren. Im folgenden Beispielcode wird zunächst ein Objekt namens „Foo“ erstellt und dann die valueOf()-Methode des Prototypobjekts von „Foo“ als Teil der Klassendefinition neu definiert. Da jede Klasse von der Object-Klasse erbt, muss das Schlüsselwort extends nicht verwendet werden.

dynamic class Foo 
{ 
    prototype.valueOf = function() 
    { 
        return "Instance of Foo"; 
    }; 
}

Dann können Sie eine valueOf()-Methode für das Prototypobjekt von „Foo“ außerhalb der Klassendefinition definieren. Dies wird im folgenden Beispiel gezeigt:

Foo.prototype.valueOf = function() 
{ 
    return "Instance of Foo"; 
};

Als dritte Möglichkeit können Sie eine feste Eigenschaft namens valueOf() als Teil der Foo-Klasse definieren. Diese Technik unterscheidet sich insofern von den anderen, als dass sie die Vererbung fester Eigenschaften mit der Prototypvererbung mischt. Jede Unterklasse von „Foo“, die valueOf() neu definieren möchte, muss das Schlüsselwort override verwenden. Im folgenden Beispielcode wird gezeigt, wie valueOf() als eine feste Eigenschaft in „Foo“ definiert wird:

class Foo 
{ 
    function valueOf():String 
    { 
        return "Instance of Foo"; 
    } 
}

AS3-Namespace

Die beiden separaten Vererbungsmechanismen, die Vererbung fester Eigenschaften und die Prototypvererbung, stellen hinsichtlich der Eigenschaften und Methoden der Hauptklassen eine interessante Herausforderung an die Kompatibilität. Die Kompatibilität mit der ECMAScript-Sprachspezifikation, auf der ActionScript aufbaut, erfordert die Verwendung der Prototypvererbung. Dies bedeutet, dass die Eigenschaften und Methoden einer Hauptklasse für das Prototypobjekt dieser Klasse definiert werden. Andererseits verlangt die Kompatibilität mit ActionScript 3.0 nach einer Vererbung fester Eigenschaften. Dies bedeutet, dass die Eigenschaften und Methoden einer Hauptklasse mithilfe der Schlüsselwörter const, var und function in der Klassendefinition definiert sind. Darüber hinaus kann die Verwendung der festen Eigenschaften anstelle der Prototypversionen eine deutliche Leistungsverbesserung zur Laufzeit bedeuten.

ActionScript 3.0 löst dieses Problem, indem für die Hauptklassen sowohl die Prototypvererbung als auch die Vererbung fester Eigenschaften verwendet wird. Jede Hauptklasse enthält zwei Sätze mit Eigenschaften und Methoden. Ein Satz wird zur Kompatibilität mit der ECMAScript-Spezifikation am Prototypobjekt definiert, der andere Satz wird zur Kompatibilität mit ActionScript 3.0 mit festen Eigenschaften und dem AS3-Namespace definiert.

Der AS3-Namespace bietet einen bequemen Mechanismus zur Auswahl zwischen den beiden Eigenschaften- und Methodensätzen. Wenn Sie den AS3-Namespace nicht verwenden, erbt eine Instanz der Hauptklasse die Eigenschaften und Methoden, die im Prototypobjekt der Hauptklasse definiert wurden. Wenn Sie den AS3-Namespace verwenden, erbt eine Instanz der Hauptklasse die AS3-Versionen, da die festen Eigenschaften gegenüber den Prototypeigenschaften immer bevorzugt werden. Anders ausgedrückt, wenn eine feste Eigenschaft verfügbar ist, sollte sie stets anstelle einer identisch benannten Prototypeigenschaft verwendet werden.

Sie können selektiv die AS3-Namespace-Version einer Eigenschaft oder Methode verwenden, indem Sie sie mit dem AS3-Namespace qualifizieren. Im folgenden Beispielcode wird die AS3-Version der Array.pop()-Methode verwendet:

var nums:Array = new Array(1, 2, 3); 
nums.AS3::pop(); 
trace(nums); // output: 1,2

Alternativ können Sie die Direktive use namespace verwenden, um den AS3-Namespace für alle Definitionen innerhalb eines Codeblocks zu öffnen. Im folgenden Beispielcode wird die Direktive use namespace verwendet, um den AS3-Namespace für die Methoden pop() und push() zu öffnen:

use namespace AS3; 
 
var nums:Array = new Array(1, 2, 3); 
nums.pop(); 
nums.push(5); 
trace(nums) // output: 1,2,5

Darüber hinaus bietet ActionScript 3.0 Compileroptionen für jeden Eigenschaftensatz, sodass Sie den AS3-Namespace auf das gesamte Programm anwenden können. Die Compileroption -as3 stellt den AS3-Namespace, die Compileroption -es die Prototypvererbungsoption dar (es steht für ECMAScript). Um den AS3-Namespace für das gesamte Programm zu öffnen, setzen Sie die Compileroption -as3 auf true und die Compileroption -es auf false. Wenn Sie die Prototypversionen verwenden möchten, setzen Sie die Compileroptionen auf die jeweils entgegengesetzten Werte. Die Compiler-Standardeinstellungen für Flash Builder und Flash Professional lauten -as3 = true und -es = false.

Wenn Sie eine der Hauptklassen erweitern und Methoden überschreiben möchten, müssen Sie verstanden haben, wie der AS3-Namespace Ihr Vorgehen beim Deklarieren einer überschriebenen Methode beeinflussen kann. Wenn Sie den AS3-Namespace verwenden, muss eine Methodenüberschreibung einer Hauptklassenmethode auch den AS3-Namespace zusammen mit dem Attribut override verwenden. Wenn Sie den AS3-Namespace nicht verwenden und eine Hauptklassenmethode in einer Unterklasse neu definieren möchten, dürfen Sie den AS3-Namespace oder das Schlüsselwort override nicht verwenden.