Argomenti avanzati

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.