Geavanceerde onderwerpen

Geschiedenis van OOP-ondersteuning in ActionScript

Omdat ActionScript 3.0 voortborduurt op vorige versies van ActionScript, is het wellicht nuttig te begrijpen hoe het ActionScript-objectmodel is ontwikkeld. ActionScript is begonnen als een eenvoudig mechanisme voor het schrijven van scripts in vorige versies van Flash professional. De programmeurs maakten echter steeds complexere toepassingen met ActionScript. Om dergelijke programmeurs tegemoet te komen, zijn er taalfuncties aan elke nieuwe uitgave toegevoegd die het maken van complexe toepassingen vereenvoudigen.

ActionScript 1.0

ActionScript 1.0 verwijst naar de taalversie die in Flash Player 6 en lager is gebruikt. Zelfs in deze vroege ontwikkelingsfase was het ActionScript-objectmodel al gebaseerd op het concept van het object als een fundamenteel gegevenstype. Een ActionScript-object is een samengesteld gegevenstype met een groep eigenschappen. In de beschrijving van het objectmodel staat de term eigenschappen voor alles dat aan een object is gekoppeld, zoals variabelen, functies of methoden.

Hoewel de eerste generatie van ActionScript de definitie van klassen met het trefwoord class niet ondersteunt, kunt u een klasse definiëren met het gebruik van een speciaal object dat een prototypeobject heet. In plaats van dat u het trefwoord class gebruikt om een abstracte klassendefinitie te maken die u instantieert in concrete objecten, zoals in talen die op klassen zijn gebaseerd zoals Java en C++, gebruiken op prototypen gebaseerde talen zoals ActionScript 1.0 een bestaand object als model (of prototype) voor andere objecten. Objecten in een taal die op klassen is gebaseerd, wijzen wellicht naar een klasse die als sjabloon dient, maar objecten in een taal die op prototypen is gebaseerd wijzen naar een ander object, het prototype, dat als sjabloon dient.

Als u een klasse in ActionScript 1.0 wilt maken, definieert u een constructorfunctie voor die klasse. In ActionScript zijn functies eigenlijk objecten, niet alleen abstracte definities. De constructorfunctie die u maakt, dient als het prototypeobject voor instanties van die klasse. De volgende code maakt een klasse met de naam Shape en definieert een eigenschap met de naam visible die standaard is ingesteld op true:

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

Deze constructorfunctie definieert een klasse Shape die u als volgt kunt instantiëren met de operator new:

myShape = new Shape();

Het constructorfunctieobject Shape() dient als het prototype voor instanties van de klasse Shape, maar kan ook als het prototype dienen voor subklassen van Shape, oftewel voor andere klassen die de klasse Shape uitbreiden.

Het maken van een klasse die een subklasse van de klasse Shape is, gebeurt in twee stappen. Maak eerst de klasse door als volgt een constructorfunctie voor de klasse te definiëren:

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

Gebruik vervolgens de operator new om te declareren dat de klasse Shape het prototype voor de klasse Circle is. Elke klasse die u maakt, gebruikt standaard de klasse Object als het prototype; dit houdt in dat Circle.prototype momenteel een algemeen object bevat (een instantie van de klasse Object). Als u wilt opgeven dat de prototype van Circle Shape is in plaats van Object, gebruikt u de volgende code om de waarde van Circle.prototype te wijzigen zodat deze een object Shape bevat in plaats van een algemeen object:

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

De klasse Shape en de klasse Circle zijn nu aan elkaar gekoppeld in een overervingsrelatie die bekend staat als de prototypeketen. Het diagram illustreert de relaties in een prototypeketen:

De basisklasse aan het eind van elke prototypeketen is de klasse Object. De klasse Object bevat een eigenschap van het type static met de naam Object.prototype die wijst naar het basisprototypeobject voor alle objecten die zijn gemaakt in ActionScript 1.0. Het volgende object in de prototypeketen in het voorbeeld is het object Shape. Dit wegens het feit dat de eigenschap Shape.prototype nooit expliciet is ingesteld, zodat het nog steeds een algemeen object bevat (een instantie van de klasse Object). De laatste koppeling in deze keten is de klasse Circle, die is gekoppeld aan zijn prototype, de klasse Shape (de eigenschap Circle.prototype bevat een object Shape).

Als we een instantie van de klasse Circle maken, zoals in het volgende voorbeeld, overerft de instantie de prototypeketen van de klasse Circle:

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

Er is reeds een eigenschap met de naam visible gemaakt als een lid van de klasse Shape. In ons voorbeeld bestaat de eigenschap visible niet als een deel van het object myCircle, alleen als een lid van het object Shape, maar de volgende coderegel geeft toch true weer:

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

De uitvoering kan controleren of het object myCircle de eigenschap visible overerft door de prototypeketen naar boven te doorlopen. Wanneer deze code wordt uitgevoerd, zoekt de uitvoering eerst in de eigenschappen van het object myCircle naar een eigenschap met de naam visible, maar vindt een dergelijke eigenschap niet. Deze zoekt vervolgens in het object Circle.prototype, maar vindt nog steeds geen eigenschap met de naam visible. Naarmate deze de prototypeketen naar boven doorloopt, wordt uiteindelijk de eigenschap visible gevonden die is gedefinieerd voor het object Shape.prototype en geeft hij de waarde van die eigenschap weer.

Om het eenvoudig te houden, zijn veel details van de prototypeketen weggelaten. Het is in plaats daarvan de bedoeling om u voldoende informatie te geven, zodat u het objectmodel van ActionScript 3.0 beter begrijpt.

ActionScript 2.0

ActionScript 2.0 introduceerde nieuwe trefwoorden zoals class, extends, public en private, waarmee u klassen op een herkenbare manier kon definiëren voor iedereen die met talen werkt die op klassen zijn gebaseerd, zoals Java en C++. Het is belangrijk om te weten dat het onderliggende overervingsmechanisme niet veranderde tussen ActionScript 1.0 en ActionScript 2.0. In ActionScript 2.0 werd alleen een nieuwe syntaxis toegevoegd voor het definiëren van klassen. De prototypeketen werkt in beide taalversies op dezelfde manier.

Met de nieuwe syntaxis die in ActionScript 2.0 werd geïntroduceerd, zoals getoond in het volgende fragment, kunt u klassen op een manier definiëren die voor vele programmeurs intuïtiever was.

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

ActionScript 2.0 heeft ook typeannotaties geïntroduceerd die werden gebruikt wanneer bij compilatie de typen werden gecontroleerd. Hierdoor kunt u declareren dat de eigenschap visible in het vorige voorbeeld slechts een Booleaanse waarde bevat. Het nieuwe trefwoord extends vereenvoudigt ook het maken van een subklasse. In het vorige voorbeeld wordt het proces van twee stappen dat nodig is in ActionScript 1.0 met het trefwoord extends in slechts één stap gerealiseerd:

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

De constructor wordt nu gedeclareerd als deel van de klassendefinitie en de klasseneigenschappen id en radius moeten ook expliciet worden gedeclareerd.

ActionScript 2.0 heeft ook ondersteuning toegevoegd voor de definitie van interfaces, waarmee u objectgeoriënteerde programma’s kunt verfijnen met formeel gedefinieerde protocollen voor interobjectcommunicatie.

ActionScript 3.0-klassenobject

Een algemeen objectgeoriënteerd programmeerparadigma, normaal gesproken geassocieerd met Java en C++, gebruikt klassen om de typen van objecten te definiëren. Programmeertalen die dit paradigma gebruiken, gebruiken doorgaans ook klassen om instanties van het gegevenstype te maken die de klasse definieert. ActionScript gebruikt klassen voor beide doeleinden, maar zijn grondslag als een op prototypen gebaseerde taal voegt een interessant attribuut toe. ActionScript maakt voor elke klassendefinitie een speciaal klassenobject dat het delen van gedrag en status toestaat. Voor veel ActionScript-programmeurs heeft dit verschil echter geen praktische gevolgen voor de code. ActionScript 3.0 is zo ontworpen dat u geavanceerde objectgeoriënteerde ActionScript-toepassingen kunt maken zonder het gebruik van, of enige kennis van, deze speciale klassenobjecten.

Het volgende diagram toont de structuur van een klassenobject die een eenvoudige klasse met de naam A vertegenwoordigt die is gedefinieerd met de instructie class A {}:

Elke rechthoek in het diagram vertegenwoordigt een object. Elk object in het diagram heeft een subscript teken A om aan te geven dat het bij klasse A hoort. Het klassenobject (CA ) bevat verwijzingen naar een getal of andere belangrijke objecten. Een instantieobject traits (TA ) slaat de instantie-eigenschappen op die binnen een klassendefinitie zijn gedefinieerd. Een klassenobject traits (TCA) vertegenwoordigt het interne type van de klasse en slaat de statische eigenschappen op die zijn gedefinieerd door de klasse (het subscript teken C staat voor ‘klasse’). Het prototypeobject (PA ) verwijst altijd naar het klassenobject waaraan het oorspronkelijk was gekoppeld via de eigenschap constructor.

Object traits

Het object traits, nieuw in ActionScript 3.0, is geïmplementeerd met het oog op prestaties. In vorige versies van ActionScript was het opzoeken van namen een tijdrovende zaak, omdat Flash Player de prototypeketen moest doorlopen. In ActionScript 3.0 verloopt het opzoeken van namen veel efficiënter en neemt het aanzienlijk minder tijd in beslag, omdat overgeërfde eigenschappen van superklassen naar het object traits van subklassen worden gekopieerd.

Het object traits is niet direct toegankelijk voor programmeercode, maar de aanwezigheid is merkbaar door prestatieverbetering en geheugenverbruik. Het object traits biedt de AVM2 gedetailleerde informatie over de lay-out en inhoud van een klasse. Met deze informatie is de AVM2 in staat de uitvoertijd aanzienlijk te verminderen, omdat deze vaak directe machine-instructies kan maken om de eigenschappen te benaderen of methoden direct aan te roepen zonder eerst tijd te moeten besteden aan het opzoeken van de naam.

Dankzij het object traits is de geheugenruimte van een object aanzienlijk kleiner dan bij een vergelijkbaar object in vorige versies van ActionScript. Als een klasse bijvoorbeeld is verzegeld (de klasse wordt niet dynamisch gedeclareerd), heeft een instantie van de klasse geen hash-tabel nodig voor dynamisch toegevoegde eigenschappen en kan deze niet meer bevatten dan een verwijzing naar objecten traits en enkele ruimten voor vaste eigenschappen die in de klasse zijn gedefinieerd. Het resultaat is dat een object dat 100 bytes geheugen in ActionScript 2.0 nodig had, nu slechts 20 bytes geheugen in ActionScript 3.0 nodig heeft.

Opmerking: Het object traits is een intern implementatiedetail en er wordt niet gegarandeerd dat het niet wordt gewijzigd of wellicht verdwijnt in toekomstige versies van ActionScript.

Prototypeobject

Elk ActionScript-klassenobject heeft een eigenschap met de naam prototype, die een verwijzing is naar het prototypeobject van de klasse. Het prototypeobject stamt uit de tijd dat ActionScript een op prototypen gebaseerde taal was. Zie Geschiedenis van OOP-ondersteuning in ActionScript voor meer informatie.

De eigenschap prototype is alleen-lezen; dit houdt in dat de eigenschap niet kan worden gewijzigd om naar andere objecten te wijzen. Dit verschilt van de klasseneigenschap prototype in vorige versies van ActionScript, het prototype kon opnieuw worden toegewezen zodat het naar een andere klasse wees. Hoewel de eigenschap prototype alleen-lezen is, is het prototypeobject waarnaar deze verwijst dit niet. Met andere woorden, er kunnen nieuwe eigenschappen aan het prototypeobject worden toegevoegd. Eigenschappen die aan het prototypeobject zijn toegevoegd, worden door alle instanties van de klasse gedeeld.

De prototypeketen, het enige overervingsmechanisme in eerdere versies van ActionScript, speelt slechts een ondergeschikte rol in ActionScript 3.0. Het primaire overervingsmechanisme, overerving van vaste eigenschappen, wordt intern afgehandeld door het object traits. Een vaste eigenschap is een variabele of methode die is gedefinieerd als deel van een klassendefinitie. De overerving van vaste eigenschappen wordt ook klassenovererving genoemd omdat dit het overervingsmechanisme is dat is gekoppeld aan trefwoorden zoals class, extends en override.

De prototypeketen biedt een alternatief overervingsmechanisme dat dynamischer is dan de overerving van vaste eigenschappen. U kunt eigenschappen niet alleen aan het prototypeobject van een klasse toevoegen als deel van de klassendefinitie, maar ook bij uitvoering via de eigenschap prototype van het klassenobject. Als u de compiler echter instelt op een strikte modus, hebt u mogelijk geen toegang tot eigenschappen die aan een prototypeobject zijn toegevoegd tenzij u een klasse declareert met het trefwoord dynamic.

Een goed voorbeeld van een klasse waarvan verschillende eigenschappen aan het prototypeobject zijn gekoppeld, is de klasse Object. De methode toString() en valueOf() van de klasse Object zijn eigenlijk functies die zijn toegewezen aan de eigenschappen van het prototypeobject van de klasse Object. De volgende code is een voorbeeld van hoe de declaratie van deze methoden er in feite moet uitzien (de daadwerkelijke implementatie verschilt iets vanwege de implementatiedetails):

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

Buiten de klassendefinitie kunt u een eigenschap aan het prototypeobject van een klasse koppelen. De methode toString() kan bijvoorbeeld ook als volgt buiten de klassendefinitie Object worden gedefinieerd:

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

In tegenstelling tot overerving van vaste eigenschappen, vereist een prototypeovererving niet het trefwoord override als u een methode in een subklasse opnieuw wilt definiëren. Bijvoorbeeld. Als u de methode valueOf() opnieuw wilt definiëren in een subklasse van de klasse Object, hebt u drie opties. Ten eerste kunt u een methode valueOf() definiëren voor het prototypeobject van de subklasse binnen de klassendefinitie. De volgende code maakt een subklasse van Object met de naam Foo en definieert de methode valueOf() opnieuw voor het prototypeobject van Foo als deel van de klassendefinitie. Omdat elke klasse overerft van Object, is het niet nodig het trefwoord extends te gebruiken.

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

Ten tweede kunt u een methode valueOf() definiëren voor het prototypeobject van Foo buiten de klassendefinitie, zoals getoond in de volgende code:

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

Ten slotte kunt u een vaste eigenschap met de naam valueOf() definiëren als deel van de klasse Foo. Deze techniek verschilt van de andere technieken in het feit dat overerving van vaste eigenschappen en prototypeovererving door elkaar wordt gebruikt. Als een subklasse van Foo valueOf() opnieuw wil definiëren, moet deze het trefwoord override gebruiken. De volgende code toont valueOf() gedefinieerd als een vaste eigenschap in Foo:

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

AS3-naamruimte

De aanwezigheid van twee afzonderlijke overervingsmechanismen, overerving van vaste eigenschappen en prototypeovererving, biedt een interessante uitdaging wat betreft de compatibiliteit ten opzichte van de eigenschappen en methoden van de kernklassen. Compatibiliteit met het concept ECMAScript Language Specification waarop ActionScript is gebaseerd, vereist het gebruik van prototypeovererving. Dit houdt in dat de eigenschappen en methoden van een kernklasse worden gedefinieerd voor het prototypeobject van die klasse. Aan de andere kant vereist compatibiliteit met ActionScript 3.0 het gebruik van de overerving van vaste eigenschappen. Dit betekent dat de eigenschappen en methoden van een kernklasse in de klassendefinitie worden gedefinieerd met gebruik van de trefwoorden const, var en function. Bovendien kan het gebruik van overerving van vaste eigenschappen in plaats van prototypeovererving tot aanzienlijk betere uitvoerprestaties leiden.

Dit probleem is in ActionScript 3.0 opgelost door het gebruik van zowel prototypeovererving als overerving van vaste eigenschappen voor de kernklassen. Elke kernklasse bevat twee sets eigenschappen en methoden. Eén set wordt voor het prototypeobject gedefinieerd om voor compatibiliteit met de ECMAScript-specificatie te zorgen en de andere set wordt met vaste eigenschappen en de AS3-naamruimte gedefinieerd om voor compatibiliteit met ActionScript 3.0 te zorgen.

De AS3-naamruimte biedt een vereenvoudigd mechanisme voor het kiezen tussen de twee sets eigenschappen en methoden. Als u geen gebruik maakt van de AS3-naamruimte, overerft een instantie van de kernklasse de eigenschappen en methoden die zijn gedefinieerd voor het prototypeobject van de kernklasse. Als u de AS3-naamruimte wel gebruikt, overerft een instantie van de kernklasse de AS3-versies, omdat vaste eigenschappen altijd de voorkeur krijgen boven prototype-eigenschappen. Met andere woorden, als er een vaste eigenschap beschikbaar is, wordt deze altijd gebruikt in plaats van een prototype-eigenschap met identieke naam.

U kunt de AS3-naamruimteversie van een eigenschap of methode selectief gebruiken door deze te kwalificeren met de AS3-naamruimte. De volgende code gebruikt bijvoorbeeld de AS3-versie van de methode Array.pop():

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

Als alternatief kunt u de aanwijzing use namespace gebruiken om de AS3-naamruimte te openen voor alle definities binnen een codeblok. De volgende code gebruikt bijvoorbeeld de aanwijzing use namespace om de AS3-naamruimte voor de methoden pop() en push() te openen:

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

ActionScript 3.0 biedt ook compileropties voor elke set eigenschappen zodat u de AS3-naamruimte op het gehele programma kunt toepassen. De compileroptie -as3 vertegenwoordigt de AS3-naamruimte en de compileroptie -es vertegenwoordigt prototypeovererving (es staat voor ECMAScript). Als u de AS3-naamruimte voor het gehele programma wilt openen, stelt u de compileroptie -as3 in op true en de compileroptie -es op false. Als u het prototypemodel wilt gebruiken, stelt u de compileropties in op de tegenovergestelde waarden. De standaard compileropties voor Flash Builder en Flash Professional zijn -as3 = true en -es = false.

Als u van plan bent kernklassen uit te breiden en methoden te overschrijven, moet u weten hoe de AS3-naamruimte de manier beïnvloedt waarop u een overschreven methode moet definiëren. Als u de AS3-naamruimte gebruikt, moet een methodeoverschrijving van een kernklassenmethode gebruikmaken van de AS3-naamruimte en van het attribuut override. Als u de AS3-naamruimte echter niet gebruikt en u wilt een kernklassenmethode in een subklasse opnieuw definiëren, moet u niet de AS3-naamruimte of het trefwoord override gebruiken.