Storia del supporto per la programmazione a oggetti in ActionScript
Poiché ActionScript 3.0 si basa sulle versioni precedenti di ActionScript, potrebbe risultare interessante comprendere come si è evoluto il modello a oggetti di ActionScript. ActionScript è nato come un semplice meccanismo per la creazione di script per le prime versioni di Flash Professional. In seguito, i programmatori hanno iniziato a creare applicazioni sempre più complesse con ActionScript. In risposta alle esigenze dei programmatori, in ogni versione successiva sono state aggiunte funzionalità di linguaggio finalizzate a facilitare la creazione di applicazioni complesse.
ActionScript 1.0
ActionScript 1.0 è la versione del linguaggio utilizzata in Flash Player 6 e versioni precedenti. Già in questa prima fase della sua evoluzione, il modello a oggetti di ActionScript si basava sul concetto di oggetto come tipo di dati fondamentale. Un oggetto di ActionScript è un tipo di dati composti con un gruppo di
proprietà
. Quando si parla di modello a oggetti, il termine
proprietà
include tutto ciò che viene associato a un oggetto, ad esempio variabili, funzioni e metodi.
Nonostante la prima generazione di ActionScript non supportasse la definizione di classi con la parola chiave
class
, era possibile definire una classe impiegando un tipo di oggetto speciale denominato oggetto prototipo. Invece di utilizzare la parola chiave
class
per creare una definizione astratta di classe a partire dalla quale creare istanze in oggetti concreti, come accade nei linguaggi basati sulle classi, quali Java e C++, i linguaggi basati sui prototipi, quali ActionScript 1.0, impiegano un oggetto esistente come modello (o prototipo) per creare altri oggetti. Se nei linguaggi basati sulle classi gli oggetti puntano a una classe che funge da modello, nei linguaggi basati sui prototipi, gli oggetti puntano invece a un altro oggetto, il loro prototipo, che funge da modello.
Per creare una classe in ActionScript 1.0, è necessario definire una funzione di costruzione per tale classe. In ActionScript, le funzioni sono veri e propri oggetti, non solo definizioni astratte. La funzione di costruzione creata funge da oggetto prototipo per le istanze della classe. Il codice seguente consente di creare una classe denominata Shape e di definire una proprietà chiamata
visible
configurata su
true
per impostazione predefinita:
// base class
function Shape() {}
// Create a property named visible.
Shape.prototype.visible = true;
Questa funzione di costruzione definisce una classe Shape dalla quale è possibile creare un'istanza mediante l'operatore
new
, come segue:
myShape = new Shape();
L'oggetto funzione di costruzione
Shape()
oltre a servire da prototipo per la creazione di istanze della classe Shape, può inoltre fungere da prototipo per la creazione di sottoclassi di Shape, vale a dire di altre classi che estendono la classe Shape.
Per creare una sottoclasse della classe Shape è necessario seguire una procedura in due fasi. In primo luogo, si deve creare la classe mediante la definizione di una funzione di costruzione per la classe, come segue:
// child class
function Circle(id, radius)
{
this.id = id;
this.radius = radius;
}
Quindi, è necessario usare l'operatore
new
per dichiarare che la classe Shape è il prototipo della classe Circle. Per impostazione predefinita, qualsiasi classe creata impiega la classe Object come prototipo, di conseguenza il valore
Circle.prototype
contiene un oggetto generico (un'istanza della classe Object). Per specificare che il prototipo di Circle deve essere Shape anziché Object, è necessario utilizzare il codice seguente per modificare il valore
Circle.prototype
in modo che contenga un oggetto Shape invece di un oggetto generico:
// Make Circle a subclass of Shape.
Circle.prototype = new Shape();
La classe Shape e la classe Circle sono ora collegate in una relazione di ereditarietà comunemente conosciuta come
catena di prototipi
. Il diagramma seguente illustra le varie relazioni in una catena di prototipi:
La classe alla base di ogni catena di prototipi è la classe Object. La classe Object contiene una proprietà statica denominata
Object.prototype
che punta all'oggetto prototype di base di tutti gli oggetti creati con ActionScript 1.0. L'oggetto successivo nella catena di prototipi è l'oggetto Shape. La proprietà
Shape.prototype
infatti non è mai stata esplicitamente impostata e contiene ancora un oggetto generico (un'istanza della classe Object). Il collegamento finale della catena è costituito dalla classe Circle, collegata al suo prototipo, la classe Shape (la proprietà
Circle.prototype
contiene un oggetto Shape).
Se create un'istanza della classe Circle, come nell'esempio seguente, tale istanza eredita la catena di prototipi della classe Circle:
// Create an instance of the Circle class.
myCircle = new Circle();
Ricordate che l'esempio comprende una proprietà denominata
visible
, membro della classe Shape. Nell'esempio, la proprietà
visible
non esiste come parte dell'oggetto
myCircle
, ma solo come membro dell'oggetto Shape, tuttavia, la riga di codice seguente restituisce
true
:
trace(myCircle.visible); // output: true
Il runtime è in grado di determinare che l'oggetto
myCircle
ha ereditato la proprietà
visible
risalendo la catena di prototipi. Quando esegue questo codice, il runtime cerca in primo luogo all'interno delle proprietà dell'oggetto
myCircle
una proprietà chiamata
visible
, ma non la trova. Il runtime esegue quindi una ricerca all'interno dell'oggetto
Circle.prototype
, ma neppure lì trova una proprietà denominata
visible
. Risalendo lungo la catena dei prototipi, il runtime trova infine la proprietà
visible
definita nell'oggetto
Shape.prototype
e restituisce il valore di tale proprietà.
Per una maggiore semplicità, in questa sezione sono stati omessi numerosi dettagli relativi alla catena di prototipi, per fornire solo le informazioni necessarie a comprendere il modello a oggetti di ActionScript 3.0.
ActionScript 2.0
In ActionScript 2.0 vengono introdotte nuove parole chiave, quali
class
,
extends
,
public
e
private
, che consentono di definire le classi in maniera simile ai linguaggi basati sulle classi, quali Java e C++. È importante tenere presente che il meccanismo di ereditarietà alla base non è cambiato con il passaggio da ActionScript 1.0 ad ActionScript 2.0. In ActionScript 2.0 è stato semplicemente aggiunto un nuovo tipo sintassi per la definizione delle classi. La catena dei prototipi funziona alla stessa maniera in entrambe le versioni del linguaggio.
La nuova sintassi introdotta da ActionScript 2.0, e illustrata nell'estratto seguente, consente di definire le classi in un modo che molti programmatori trovano più intuitivo:
// base class
class Shape
{
var visible:Boolean = true;
}
Tenete inoltre presente che in ActionScript 2.0 sono state introdotte anche le annotazioni di tipo da utilizzare con la verifica del tipo in fase di compilazione, che consentono di dichiarare che la proprietà
visible
dell'esempio precedente contiene solamente un valore booleano. Anche la nuova parola chiave
extends
semplifica il processo di creazione delle sottoclassi. Nell'esempio seguente, la procedura in due fasi necessaria in ActionScript 1.0 viene portata a termine in una sola fase, grazie all'introduzione della parola chiave
extends
:
// child class
class Circle extends Shape
{
var id:Number;
var radius:Number;
function Circle(id, radius)
{
this.id = id;
this.radius = radius;
}
}
La funzione di costruzione viene ora dichiarata come parte della definizione della classe e le proprietà di classe
id
e
radius
devono anch'esse essere dichiarate esplicitamente.
In ActionScript 2.0 è stato inoltre introdotto il supporto della definizione di interfacce, che consente di rendere ancora più specifici i programmi orientati agli oggetti mediante protocolli formalmente definiti per la comunicazione tra oggetti.
Oggetto classe di ActionScript 3.0
Un comune paradigma di programmazione orientato agli oggetti, generalmente associato a Java e C++, impiega le classi per definire i tipi di oggetti. I linguaggi di programmazione che adottano questo paradigma tendono anch'essi a utilizzare le classi per costruire istanze del tipo di dati definito dalla classe stessa. ActionScript impiega le classi per entrambi questi scopi, tuttavia, le sue origini di linguaggio basato sui prototipi gli attribuisce un'interessante caratteristica. Per ogni definizione di classe in ActionScript viene creato uno speciale oggetto di classe che consente la condivisione di comportamento e stato della classe. Per numerosi programmatori ActionScript tuttavia, tale distinzione potrebbe non avere alcuna implicazione pratica in termini di codifica. ActionScript 3.0 è stato progettato in modo da consentire la creazione di sofisticate applicazioni ActionScript orientate agli oggetti senza la necessità di usare, o addirittura comprendere, tali particolari oggetti di classe.
Il diagramma seguente riporta la struttura di un oggetto di classe che rappresenta una semplice classe denominata A, definita con l'istruzione
class A {}
:
Ogni rettangolo del diagramma rappresenta un oggetto. Ogni oggetto del diagramma è seguito da una lettera in carattere pedice a indicare che appartiene alla classe A. L'oggetto di classe (CA) contiene riferimenti a vari altri oggetti importanti. L'oggetto traits dell'istanza (TA) conserva in memoria le proprietà dell'istanza definite nella definizione della classe. L'oggetto traits della classe (TCA) rappresenta il tipo interno della classe e conserva in memoria le proprietà statiche definite dalla classe (la lettera C in carattere pedice sta per “classe”). L'oggetto prototype (PA) si riferisce sempre all'oggetto di classe al quale era originariamente associato mediante la proprietà
constructor
.
Oggetto traits
L'oggetto traits, una novità di ActionScript 3.0, è stato implementato ai fini delle prestazioni. Nelle versioni precedenti di ActionScript, il processo di ricerca dei nomi poteva risultare lento e laborioso, in quanto Flash Player doveva risalire l'intera catena di prototipi. In ActionScript 3.0, le operazioni di ricerca dei nomi sono molto più efficienti e veloci, poiché le proprietà ereditate vengono copiate dalle superclassi negli oggetti traits delle sottoclassi.
L'oggetto traits non è direttamente accessibile dal codice di programmazione, tuttavia, la sua presenza è riscontrabile in termini di miglioramenti delle prestazioni e dell'uso della memoria. L'oggetto traits fornisce ad AVM2 informazioni dettagliate sul layout e il contenuto delle classi. Grazie a tali informazioni, AVM2 è in grado di ridurre sensibilmente i tempi di esecuzione, in quanto può generare spesso istruzioni dirette alla macchina per l'accesso immediato a proprietà o la chiamata di metodi, senza che siano necessarie lente e laboriose ricerche di nomi.
L'oggetto traits consente inoltre di ridurre sensibilmente l'occupazione della memoria di un oggetto rispetto a quanto poteva occupare un oggetto simile nelle versioni precedenti di ActionScript. Ad esempio, se una classe è chiusa (vale a dire, non è dichiarata dinamica), un'istanza di tale classe non richiede una tabella hash per le proprietà aggiunte dinamicamente e può contenere qualcosa in più che un semplice puntatore agli oggetti traits e alcuni slot per le proprietà fisse definite nella classe. Di conseguenza, a un oggetto che richiedeva 100 byte di memoria in ActionScript 2.0 potrebbero bastare 20 byte in ActionScript 3.0.
Nota:
l'oggetto traits è un dettaglio interno di implementazione e potrebbe essere modificato o addirittura eliminato nelle versioni future di ActionScript.
Oggetto prototype
Ogni oggetto della classe di ActionScript presenta una proprietà chiamata
prototype
che funge da riferimento all'oggetto prototype della classe. L'oggetto prototype è un retaggio delle origini di ActionScript come linguaggio basato sui prototipi. Per ulteriori informazioni, vedete Storia del supporto per la programmazione a oggetti in ActionScript.
La proprietà
prototype
è una proprietà di sola lettura, vale a dire che non può essere modificata per puntare a oggetti differenti. Ciò la differenzia dalla proprietà di classe
prototype
delle versioni precedenti di ActionScript, dove era possibile riassegnare il prototipo in modo che puntasse a una classe diversa. Nonostante la proprietà
prototype
sia di sola lettura, l'oggetto prototype a cui fa riferimento non lo è. In altre parole, è possibile aggiungere nuove proprietà all'oggetto prototype. Le proprietà aggiunte all'oggetto prototype vengono condivise tra tutte le istanze della classe.
La catena di prototipi, che era il solo meccanismo di ereditarietà delle versioni precedenti di ActionScript, ha soltanto un ruolo secondario in ActionScript 3.0. Il sistema di ereditarietà primario, ovvero l’ereditarietà delle proprietà fisse, viene gestito internamente dall’oggetto traits. Una proprietà fissa è una variabile o metodo definito in una definizione di classe. L'ereditarietà delle proprietà fisse è chiamata anche ereditarietà di classe, in quanto il meccanismo di ereditarietà è associato a parole chiave quali
class
,
extends
e
override
.
La catena di prototipi offre un meccanismo di ereditarietà alternativo molto più dinamico dell'ereditarietà di proprietà fisse. Potete aggiungere proprietà all’oggetto prototype di una classe non solo come parte della definizione della classe, ma anche in fase di runtime mediante la proprietà
prototype
dell’oggetto di classe. Tenete presente, tuttavia, che se impostate il compilatore in modalità rigorosa, potreste non essere in grado di accedere a proprietà aggiunte a un oggetto prototype a meno che non dichiariate una classe con la parola chiave
dynamic
.
Un buon esempio di classe con numerose proprietà associate all'oggetto prototype è costituito dalla classe Object. I metodi
toString()
e
valueOf()
della classe Object sono in realtà funzioni assegnate a proprietà dell'oggetto prototype della classe Object. L'esempio seguente illustra come la dichiarazione di tali metodi potrebbe, teoricamente, apparire (l'effettiva implementazione è leggermente differente a causa di dettagli di implementazione):
public dynamic class Object
{
prototype.toString = function()
{
// statements
};
prototype.valueOf = function()
{
// statements
};
}
Come accennato in precedenza, potete associare una proprietà all'oggetto prototype di una classe al di fuori della definizione della classe. Ad esempio, anche il metodo
toString()
può essere definito al di fuori della definizione della classe Object, come segue:
Object.prototype.toString = function()
{
// statements
};
A differenza dell'ereditarietà di proprietà fisse tuttavia, l'ereditarietà di prototipi non richiede la parola chiave
override
per la ridefinizione di un metodo in una sottoclasse. Ad esempio, se desiderate ridefinire il metodo
valueOf()
in una sottoclasse della classe Object, avete a disposizione tre diverse opzioni. In primo luogo, potete definire un metodo
valueOf()
sull'oggetto prototype della sottoclasse, all'interno della definizione della classe. Il codice seguente consente di creare una sottoclasse di Object chiamata Foo e di ridefinire il metodo
valueOf()
sull'oggetto prototype di Foo, nell'ambito della definizione della classe. Poiché ogni classe eredita da Object, non è necessario usare la parola chiave
extends
.
dynamic class Foo
{
prototype.valueOf = function()
{
return "Instance of Foo";
};
}
In secondo luogo, potete definire un metodo
valueOf()
sull'oggetto prototype di Foo al di fuori della definizione della classe, come illustrato nel codice seguente:
Foo.prototype.valueOf = function()
{
return "Instance of Foo";
};
Infine, potete definire una proprietà fissa denominata
valueOf()
come parte della classe Foo. Questa tecnica si differenzia dalle altre in quanto mescola il sistema di ereditarietà di proprietà fisse con il sistema di ereditarietà di prototipi. Tutte le sottoclassi di Foo che desiderano ridefinire
valueOf()
devono utilizzare la parola chiave
override
. Nel codice seguente è illustrato
valueOf()
definito come proprietà fissa di Foo:
class Foo
{
function valueOf():String
{
return "Instance of Foo";
}
}
Spazio dei nomi di AS3
L'esistenza di due meccanismi di ereditarietà distinti, l'ereditarietà di prototipi e di proprietà fisse, pone un'interessante problema di compatibilità nell'ambito delle proprietà e dei metodi delle classi principali. La compatibilità con la specifica del linguaggio ECMAScript su cui è basato ActionScript prevede l'uso dell'ereditarietà di prototipi, ovvero le proprietà e i metodi di una classe principale devono essere definiti sull'oggetto prototype della classe. D'altro canto, la compatibilità con ActionScript 3.0 richiede l'uso dell'ereditarietà di proprietà fisse, ovvero che le proprietà e i metodi di una classe principale vengano definite nell'ambito della definizione della classe mediante le parole chiave
const
,
var
e
function
. Inoltre, l'uso di proprietà fisse in luogo dei prototipi può portare a significativi incrementi delle prestazioni in fase di runtime.
ActionScript 3.0 risolve il problema utilizzando sia l'ereditarietà di prototipi che di proprietà fisse per le classi principali. Ogni classe principale contiene due serie di proprietà e metodi. Una serie viene definita sull'oggetto prototype per la compatibilità con la specifica ECMAScript, mentre l'altra serie viene definita con proprietà fisse e lo spazio dei nomi di AS3 per la compatibilità con ActionScript 3.0.
Lo spazio dei nomi di AS3 fornisce un pratico meccanismo che consente di scegliere tra le due serie di metodi e proprietà. Se non utilizzate lo spazio dei nomi di AS3, un'istanza di classe principale eredita le proprietà e i metodi definiti nell'oggetto prototype della classe principale. Se invece decidete di utilizzare lo spazio dei nomi di AS3, un'istanza di classe principale eredita le versioni di AS3, in quanto le proprietà fisse hanno sempre la precedenza rispetto alle proprietà prototype. In altre parole, se è disponibile, la proprietà fissa viene sempre utilizzata al posto di una proprietà prototype con nome identico.
Per scegliere di usare la versione dello spazio dei nomi di AS3 di una proprietà o di un metodo, è necessario qualificare la proprietà o il metodo con lo spazio dei nomi di AS3. Ad esempio, il codice seguente impiega la versione AS3 del metodo
Array.pop()
:
var nums:Array = new Array(1, 2, 3);
nums.AS3::pop();
trace(nums); // output: 1,2
Oppure, potete utilizzare la direttiva
use namespace
per aprire lo spazio dei nomi AS3 di tutte le definizioni racchiuse in un blocco di codice. Ad esempio, il codice seguente impiega la direttiva
use namespace
per aprire lo spazio dei nomi di AS3 dei metodi
pop()
e
push()
:
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 presenta inoltre opzioni del compilatore per ciascuna serie di proprietà, per consentire l'applicazione dello spazio dei nomi di AS3 all'intero programma. L'opzione del compilatore
-as3
rappresenta lo spazio dei nomi di AS3, mentre l'opzione del compilatore
-es
rappresenta l'opzione di ereditarietà dei prototipi (
es
sta per ECMAScript). Per aprire lo spazio dei nomi di AS3 per l'intero programma, impostate l'opzione del compilatore
-as3
su
true
e l'opzione del compilatore
-es
su
false
. Per utilizzare le versioni dei prototipi, impostate le opzioni del compilatore sui valori opposti. Le impostazioni predefinite del compilatore per Flash Builder sono
-as3 = true
e
-es = false
.
Se avete intenzione di estendere una delle classi principali e di sostituire uno o più metodi, dovete sapere come lo spazio dei nomi di AS3 può influenzare la modalità di dichiarazione di un metodo sostituito. Se utilizzate lo spazio dei nomi AS3, anche in ogni sostituzione di metodo di una classe principale dovete utilizzare lo spazio dei nomi AS3 insieme all'attributo
override
. Se non usate lo spazio dei nomi AS3 e desiderate ridefinire un metodo di classe principale all'interno di una sottoclasse, non dovete utilizzare lo spazio dei nomi AS3 né la parola chiave
override
.
|
|
|