Mindenekelőtt tekintsük át, hogy az ActionScript hogyan is tekint az objektumokra, s azok osztályaira. Mint minden ismert objektum-orientált nyelvben, az AS-ben is az objektumokat úgy fogják fel, mint bizonyos tulajdonságokkal, és műveletekkel rendelkező dolgokat. Az egész elgondolás a mindennapi élet megfigyeléseinek analógiájára történt, ugyanis az emberi természet alapvetően osztályozó jellegű. Vagyis születésünktől fogva a körülöttünk lévő dolgokat megpróbáljuk valamiféle kategóriákba sorolni, s mint tudjuk, ezen kategóriák helyes megválasztása a társadalmi lét alappillére. Az első objektum-orientált nyelv tervezőinek szeme előtt is ez a példa állt, s így tűnik mindez annyira természetesnek. Az AS tervezői számos előre megírt olyan kategóriát építettek a nyelvbe, amelyekről úgy gondolták, hogy a mindennapi programozói életet megkönnyíthetik. (Igazuk is volt.). Az kategóriák vagyis az osztályok egy általános meghatározást jelentenek, minthogy az autók halmaza is egy ilyen általános fogalom, ám semmit nem mond az utca túlsó oldalán lévő Suzukiról. Az osztály mint „halmaz” egy-egy eleme az osztály egy példánya, vagyis egy objektum. Ez az objektum rendelkezik osztályának minden tulajdonságával, de ezeknek konkrét értéket is adhatunk. (Félreértés ne essék, az osztály egyes tulajdonságainak lehet általános értéke is.) Vagyis az autós példánál maradva, az autók osztályának egy tulajdonsága a szín, objektuma a Suzuki, aminek a színe a fehér. Lássuk, hogyan lehet objektumot létrehozni AS-ben:
var varName:datatype = new ClassName();
varName.prop1 varName[prop1]
varName.Method();
var oDynamicObj:Object = new Object(); oDynamicObj.dynamicProperty = “New property value”; oDynamicObj.dynamicMethod = function():Void { trace(“Method invoked”); };
function Circle(x, y, r) { this.x = x; this.y = y; this.r = r; } function moveRel(ox, oy) { this.x += ox; this.y += oy; } c = new Circle(2, 5, 4); c.moveRel(3, 5); c.visibility = true; delete c.visibility;
A dinamikus osztálymodellel szemben vezette be a JavaScript 2.0 után az ActionScript 3.0 is az osztály alapú szemléletet. Ebben a szemléletben leginkább a Java osztály felépítésére hasonlító elemeket vezettek be. Így lehetőséget adnak a programozónak szigorú típusosság használatára is. Osztályt és interface-t mindig külön ún. ActionScript(*.as) file-ban, amit ugyanúgy kell elnevezni, mint a definiált osztályt vagy interface-t. Az osztály nevére ugyanolyan szabályok érvényesek, mint a változókra, további konvenció, hogy nagybetűvel kezdődnek. Az osztályokat csomagokba kell foglalni, szintaktikailag másképpen kell jelölni, mint Javaban, de ugyanaz a csomag jelentése. Ha elkészültünk az osztállyal, akkor ugyanolyan rendszerben, mint ahogy Javaban a class file-okat helyeztük el, kell az ActionScript file-unkat elhelyezni a ClassPath-hez képest. Tehát itt is az osztályok egy rendszerezésének a lehetőségét adja a csomag rendszer.
package geometry { class Circle { private var x:Number, y:Number; private var r:Number; function Circle(x:Number, y:Number, r:Number) { this.x = x; this.y = y; this.r = r; } function moveRel(ox:Number, oy:Number):Void { x += ox; y += oy; } function toString():String { return "{x: "+x+", y: "+y", r: "+r"}"; } } } import Circle; var c:Circle = new Circle(100, 100, 50); c.moveRel(3, 5);
Bevezeti az osztály (class) fogalmát, mely a szigorú típusosságra épít. Engedményként két lehetőséget hagynak, amivel a szigorú típusosságot ki lehet kerülni: az egyik, hogy osztályon belül lehet nem szigorúan típusos változó, a másik a dinamikus osztályok használatának a lehetősége. Ha egy osztályt dinamikusnak definiálunk, akkor ki lehet terjeszteni új mezőkkel.
Circle.as:
package shape { dynamic class Circle { private var x:Number, y:Number; private var r:Number; function Circle(x:Number, y:Number, r:Number) { this.x = x; this.y = y; this.r = r; } } var circle:Circle = new Circle(100, 100, 50); circle.color = 0xAACC33; //itt nem lesz fordítási hiba mivel dynamic - nak deklaráltuk az osztályt, ellenkező esetben szólna a fordító }
Osztályokon négy módosítótagot értelmez az AS3:
Hozzáférés szabályozásra két hozzáférési kategóriát vezettek be az AS2-ben: publikus (public) és privát (private), de a privát hozzáférési kategória inkább felelt
meg egy védett hozzáférési kategóriának, mert a leszármazott osztályokban is látható volt a privát tag.
AS3-ban bevezették a protected és az internal névtereket is, sőt a régiek jelentése is megváltozott. A névterek és jelentésük tehát:
Ezeken kívül metódusokra még két módosító létezik:
Az osztályok privát adattagjaihoz szorosan kapcsolódik két, nem kötelezően, de ajánlottan definiálható függvény, a getter/setter függvények, nevükből adódóan az adattagok beállító, illetve lekérdező függvények. Az a konvenció, hogy a privát adattagokat „_” jellel kezdjük, épp innen ered, ugyanis a getter/setter függvényeket így logikusan lehet definiálni. Az ilyen függvényekre erősen rá akarnak szoktatni a fejlesztők, mivel egy-egy külön kulcsszót definiáltak számukra (get, set). Vagyis egy privát változó korrekt definiálása az osztályban a következőképp adódik:
class Car { private var _make:String = null; public function get make():String { return _make; } public function set make(sMake:String):Void { _make = sMake; } }
Mivel az ilyen metódusokat is csak az osztály tartalmazza, nem használhat az utasításai közt privát, és public adattagokat, sem thist.
Saját objektum létrehozásához egy konstruktor műveletet lehet definiálni, aminek ugyanolyan nevet kell adni, mint amilyen típusú objektumot ő létre fog hozni. Konstruktorból csak egy lehet, és csakis public lehet. Természetesen a konstruktor függvényeknél is használható a törzsben a this kulcsszó. Mikor meghívunk egy konstruktort, akkor a Flash rejtett paraméterként átadja a thist. A konstruktor törzsben is lehet definiálni az objektum metódusait, bár ez nem ajánlott, mert így valahányszor meghívjuk a konstruktort, annyiszor lesz definiálva minden metódus.
Öröklő osztálynál a szülőosztály konstruktorát meghívhatjuk, ha a super() metódust meghívjuk, illetve a szülőosztály attribútumai és metódusai is elérhetőek, a super. prefixszel (pl. this.x = super.getX(); ).
function Circle(radius) { this.radius = radius; this.getArea = function() { return Math.PI*this.radius*this.radius; }; this.getDiameter = function() { return 2*this.radius; }; } var myCircle = new Circle(55); trace(myCircle.getArea()); trace(myCircle.getDiameter());
Felsorolási típusok ugyan nincsenek a nyelvben, de szimulálhatóak, ha egy final osztályba konstans static adattagokként felvesszük a lehetséges értékeket. Például:
public final class Colour { public static const BLACK:Number = 0x000000; public static const WHITE:Number = 0xFFFFFF; }
Mivel minden függvény egy Function objektum, így minden függvény rendelkezik egy call() és egy apply() tagfüggvénnyel. Ha egy függvény törzsében azt szeretnénk, hogy a this máshova mutasson, akkor használhatjuk a call() utasítást. Például:
myObject.myMethod(1, 2, 3);
myObject.myMethod.call(myOtherObject, 1, 2, 3)
Math.sin(Math.PI / 4) Math.sin.call(null, Math.PI / 4) És egy utolsó szemléltető példa call() - ra: function myObject() { //empty } function myMethod(obj) { trace("this == obj? " + (this == obj)); } var obj:Object = new myObject(); myMethod.call(obj, obj); //Az eredmény: this == obj? true
Math.atan2(1, 0) Math.atan2.apply(null, [1, 0]) function theFunction() { trace(arguments); } // létrehozunk egy új tömböt, amit paraméterként átadunk az apply() - nak var firstArray:Array = new Array(1,2,3); theFunction.apply(null,firstArray); // Az eredmény: 1,2,3 // létrehozunk egy második tömböt, amit paraméterként átadunk az apply() - nak var secondArray:Array = new Array("a", "b", "c"); theFunction.apply(null,secondArray); // Az eredmény: a,b,c
function theFunction() { trace("this == myObj? " + (this == myObj)); trace("arguments: " + arguments); } var myObj:Object = new Object(); var firstArray:Array = new Array(1,2,3); var secondArray:Array = new Array("a", "b", "c"); theFunction.apply(myObj,firstArray); // Eredmény: // this == myObj? true // arguments: 1,2,3 theFunction.apply(myObj,secondArray); // Eredmény: // this == myObj? true // arguments: a,b,c
<excludeAssets> <asset name=" className1 "></asset> <asset name=" className2 "></asset> ... </excludeAssets>
Az AS3 szemléletében egy osztály három dologból áll össze:
Tulajdonságok azok az adatok, amik összetartják az objektumokat, például egy Dal objektumnak van szerző illetve cím tulajdonsága, míg egy MovieClip objektumnak rotation, x, height, alpha tulajdonságai. Bizonyos szempontból gondolhatunk úgy a tulajdonságokra, mint az adott objektum alobjektumaira.
Tulajdonságokat a pont szintaxissal érhetünk el. Egy négyzet szélességére például így hivatkozhatunk:
square.width = 10;
A metódusok olyan műveletek, amit egy objektum tud elvégezni. Például egy MovieClip képes lejátszani magát a play() metódussal. A metódusok a tulajdonságokhoz hasonlóan érhetőek el.
Az események olyan valamik, amikről az ActionScript tud, és hatásukra valamely műveletet hajt végre. Az eseményeket az eseménykezelő vezérli.
Zárt osztályok
AS3-ban a legtöbb osztály zárt. Ez azt jelenti, hogy nem lehet hozzájuk adni dinamikus objektumokat példányosítás után.
Ami eddig az osztályunkban szerepelt az benne is marad. Azonban nincs lehetőség például egy szám elhelyezésére az osztályban, hacsak nincs dinamikus-ként definiálva.
Például ilyen dinamikus osztály a MovieClip, amin bármikor elhelyezhetünk változókat vagy függvényeket.
Hogy egy osztály dinamikus vagy nem azt könnyen kideríthetjük az új kinézetű Súgó segítségével: megnézhetjük pl., hogy dinamikus osztály-e az Object.
A class mellett ez áll: public dynamic class Object.
Tehát dinamikus, így elhelyezhetünk rajta gond nélkül új változókat, s a fordító nem fog hibát dobni.
Namespace-ek
A namespace-ek deklarálásának lehetősége bizonyára ismerős azoknak, akik más, komolyabb programnyelvekkel dolgoznak. Pár namespace ismerős lesz: private, public, protected.
Ezek a láthatóságért felelnek (lásd C++).
Meghatározzák, hogy egy adott metódust, vagy változót honnan lehet elérni. Most már lehetőségünk van saját láthatóságot is definiálni, ami például megakadályozza a névütközést azonos nevű metódusok egy osztályon belüli deklarálása esetén, de akár xml kezelésnél is hasznosak lehetnek.
Az osztály objektum
Az általános objektum-orientált programozási paradigmában az osztályokat arra használjuk, hogy objektumok típusát definiáljuk velük. Azok a programozási nyelvek, amelyek ezt a paradigmát használják arra is hajlamosak, hogy az osztályokat arra használják, hogy olyan adattípusok példányait hozzák létre, amit az osztály maga definiált. Az ActionScript az osztályokat mind a két célra használja, de ehhez a prototípus alapúság egy érdekes karakterisztikát tesz hozzá. Az ActionScript minden osztály létrehozásakor készít egy speciális osztály objektumot, ami képes megosztani a viselkedést és az állapotokat is. Az ActionScript 3.0-t úgy tervezték meg, hogy kifinomult objektum-orientált ActionScript alkalmazást lehessen vele írni, anélkül hogy használná, vagy egészen megértené ezeknek az objektumoknak a működését a programozó. A haladó programozók számára, akik ki akarják használni ezeknek az osztály objektumoknak az előnyeit ennek fejezetnek a megvitatása lesz az ugrás a mélybe.
A következő diagram azt mutatja, hogyan épül fel egy osztály objektum, ami az A osztályt reprezentálja és a class A {} utasítással definiáltuk:
Az ábrán minden téglalap egy objektumot jelent. A diagramon minden objektumnak van az alsó indexében egy karakter. Az „A” azt jelképezi, hogy ő az A osztályhoz tartozik. Az osztály objektum (CA) referenciát tartalmaz számos más fontos objektumra. A példány jelleg objektuma (TA) tárolja a példány attribútumait, amit az osztály definíción belül hoztunk létre. Az osztály jelleg objektuma (TCA) jeleníti meg az osztály belső típusait, és az osztályban definiált statikus attribútumokat. A prototípus objektum (PA) mindig azonosítja azt az osztály objektumot, amihez eredetileg a konstruktorral lett hozzácsatolva.
A jelleg objektum
A jelleg objektum, egy új objektumtípus az ActionScript 3.0-ban. Ezt az objektumot a teljesítményt tartották szem előtt tartásával valósították meg. Az előző ActionScript verziókban, a nevek kikeresése időigényes folyamat volt, addig tartott, amíg a Flash Player végigment a prototípus láncon. ActionScript 3.0-ban, a nevek kikeresése sokkal hatékonyabb és gyorsabb lett, mert az öröklött attribútumokat lemásolódnak a leszármazott osztály jelleg objektumába.
A jelleg objektum nem érhető el direkt módon a programkódból, de megléte látható a teljesítményjavulásban és a memóriahasználtban. A jelleg objektum információkat ad az osztály szerkezetről és tartalmáról az AVM2-nek. Ezen tudás birtokában az AVM2 képes a szembetűnően csökkenteni a futtatási időt, mert gyakran olyan direkt gépet generál, ami hozzáfér az attribútumokhoz, vagy metódusokat tud direkt meghívni anélkül, hogy az időigényes név keresést használná.
Köszönhetően a jelleg objektumoknak az objektumok memória lenyomata sokkal kisebb, mint az előző ActionScript verziókban. Például ha egy osztály zárt, akkor egy ilyen osztály példányának nem kell hashtábla, hogy számontartsuk a dinamikus attribútumok hozzáadása szempontjából, és mégis egy kicsivel többet tárol el a jelleg objektum, mint egy mutató. Vagyis hogy a számok nyelvén kifejezzük a dolgot: egy olyan objektum, aminek a helyfoglalása ActionScript2.0-ban 100 bájt volt az ActionScript 3.0-ban olyan 20 bájt memóriát foglal el.
A prototípus objektum
// base class function Shape() {} // Create a property named visible. Shape.prototype.visible = true;
myShape = new Shape();
// child class function Circle(id, radius) { this.id = id; this.radius = radius; }
// Make Circle a subclass of Shape. Circle.prototype = new Shape();
// Create an instance of the Circle class. myCircle = new Circle();
trace(myCircle.visible); // true
// child class class Circle extends Shape { var id:Number; var radius:Number; function Circle(id, radius) { this.id = id; this.radius = radius; } }
public dynamic class Object { prototype.toString = function() { // utasítások }; prototype.valueOf = function() { // utasítások }; }
Object.prototype.toString = function() { // uatsítások };
dynamic class Foo { prototype.valueOf = function() { return "A Foo egy példánya"; }; }
Foo.prototype.valueOf = function() { return "A Foo egy példánya"; };
class Foo { function valueOf() { return "A Foo egy példánya"; } }
Az öröklődés modellezése kicsit bonyolultabb, de elérhető a dinamikus kötés megvalósítása annak függvényében, hogy milyen konstruktor hozta létre az
objektumot.
Példa öröklődés szimulálására
function Téglalap(magasság, szélesség) { this.magasság = magasság; this.szélesség = szélesség; } Téglalap.prototype.getTerület = function() { return (this.magasság * this.szélesség); } Téglalap.prototype.toString = function() { return ("a="+this.magasság+", b=" + this.szélesség + " -> T="+this.getTerület()); } function Négyzet(oldalszélesség) { Téglalap.call(this, oldalszélesség, oldalszélesség); } Négyzet.prototype = new Téglalap; t1 = new Téglalap(10, 20); n1 = new Négyzet(15); n2 = new Négyzet(Infinity); trace(t1); // a=10, b=20 -> T=200 trace(n1); // a=15, b=15 -> T=225 trace(n2); // a=Infinity, b=Infinity -> T=Infinity
Az AS-ben lehetőség van egy tetszőleges hosszú öröklődési lánc kialakítására. Öröklődéskor az új, úgynevezett alosztály (SubClass) örökli a már meglévő
osztály (SuperClass) tulajdonságait, illetve függvényeit, mindazonáltal további metódusokat és adattagokat definiálhatunk az alosztályban, ezért is
nevezik az eljárást kiterjesztésnek (extends).
Öröklődésre az extends kulcsszó használható:
class SubClass extends SuperClass {}
Sok beépített osztályból is lehet származtatni, de például nem származtathatunk a TextField osztályból, és a statikus osztályokból sem, mint: Math, Key és Mouse osztály. Ha nem hívjuk meg a super() metódussal a szülő osztály konstruktorát a leszármazott konstruktorából, akkor a fordító automatikusan generál egy hívást a közvetlen szülőjének konstruktorára paraméterek nélkül, és ez lesz a metódus első utasítása. Ha a szülőben nincs konstruktor, akkor a fordító készít egy üreset, és meghívja ezt a leszármazottból. Azonban ha a szülőben paraméterek is vannak a konstruktorban, akkor a leszármazottban kell írni konstruktort, ami meghívja a szülő konstruktorát a megfelelő paraméterekkel. Felmerülhet annak az igénye, hogy a kiterjesztett osztályban egy olyan metódust akarunk írni, amely nevében megegyezik a szülő osztályban szereplő metódussal.(Pl. Egy toString() művelet másfajta implementációjára lehet gondolni.) Ez egyszerűen megtehető, mivel ekkor a származtatott osztály metódusa fog lefutni a metódus hívásakor (vagyis overrideoltuk a metódust).
A változók és konstansok öröklődnek, de nem definiálhatóak felül, a statikus adattagok (tehát az osztályváltozók) pedig nem öröklődnek, de elérhetőek, viszont azonos nevű változót létrehozva az elfedi az osztályváltozót.
Ha több összefüggő osztályunk is van, például:
// In Square.as: class Square {} // In Circle.as: class Circle {} // In Triangle.as: class Triangle {}
var dataInstance = new com.xyzzycorporation.Data();
var dataInstance:com.xyzzycorporation.Data = new Data();
// In the file com/xyzzycorporation/util/users/UserClass.as class com.xyzzycorporation.util.users.UserClass { ... }
import com.xyzzycorporation.util.users.UserClass;
var myUser:UserClass = new UserClass();
import com.xyzzycorporation.util.*;
Az öröklődés használatával tehát lehetőség nyílik osztályok tulajdonságainak, és metódusainak „átvételére”. Viszont mi van akkor, ha egy olyan osztályt
szeretnénk, amely egy időben több másik osztállyal áll SubClass-SuperClass viszonyban? Ez az elgondolás a polimorfizmus. Több magas szintű programozási
nyelv megengedi a több osztályból való származtatást, pl. a C++ is, de az ActionScript nem tartozik ezek közé. Viszont ennek orvoslására találták ki az
interface-eket.
Interfészeket az interface kulcsszó segítségével hozhatunk létre:
public interface interfaceName { az interface metódusainak deklarációi } public interface interface2Name { az interface metódusainak deklarációi }
class className implements interfaceName, interface2Name {az interface metódusainak megvalósítása}