ActionScript 3

20. Interfacce

I veterani di ActionScript 2 avranno già familiarità con le interfacce, un metodo molto utile per fornire strumenti standard a classi non necessariamente in relazione fra loro.

Le interfacce non definiscono esattamente un tipo di dato come le classi, infatti non è possibile creare un'istanza di un interfaccia, è possibile implementare un'interfaccia solo attraverso la dichiarazione di una classe.

Le interfacce vengono usate per dichiarare metodi che dovranno essere obbligatoriamente definiti da tutte le classi che implementeranno l'interfaccia in questione, non potranno quindi contenere attributi di nessun tipo nè definizioni di metodi, ma solo dichiarazioni di metodi.

La classe che poi implementerà l'interfaccia, dovrà riportare la medesima dichiarazione del metodo così come si presenta nell'interfaccia, e fornire obbligatoriamente una definizione del metodo (corpo del metodo).

Vediamo due interfacce di esempio ...

InterfacciaUno.as

package
{
	public interface InterfacciaUno
	{
		function MetodoInterfacciaUno(parametro:uint = 5):uint;
		function AltroMetodoInterfacciaUno(parametro:uint, altroparametro:Boolean = false):uint;
	}
}

InterfacciaDue.as

package
{
	public interface InterfacciaDue
	{
		function MetodoInterfacciaDue(parametro:String = null):uint;
		function AltroMetodoInterfacciaDue(parametro:uint = 10, altroparametro:Boolean = true):uint;
	}
}

Come avrete notato dal codice soprastante, non ho specificato alcun modificatore di accesso nella dichiarazione dei metodi d'interfaccia, poichè ActionScript 3 non lo consente.

A seguire la dichiarazione di una classe "Oggetto" che implementa entrambe le interfacce riportate sopra, attraverso la direttiva implements, che consente di implementare più interfacce separate da una virgola :

Oggetto.as

package
{
	import InterfacciaUno;
	import InterfacciaDue;

	public class Oggetto implements InterfacciaUno, InterfacciaDue
	{
		public function Oggetto():void
		{
			// Costruttore
		}

		public function MetodoInterfacciaUno(par:uint = 3):uint
		{
			return par + 2;
		}

		public function AltroMetodoInterfacciaUno(par:uint, altroparametro:Boolean = false):uint
		{
			if (altroparametro) return par + 2;
			else return par - 2;
		}

		public function MetodoInterfacciaDue(parametro:String = null):uint
		{
			return parametro.length + 1;
		}

		public function AltroMetodoInterfacciaDue(parametro:uint = 10, altroparametro:Boolean = true):uint
		{
			if (altroparametro) return parametro * 2;
			else return parametro / 2;
		}
	}
	
}

Il codice soprastante viene compilato senza errori e funziona correttamente.
ActionScript 3 infatti ci consente delle modifiche minime alla dichiarazione dei metodi :

  • Possiamo applicare un modificatore di accesso al metodo (public, private ...)
  • E' possibile cambiare i nomi dei parametri a patto che rimangano dello stesso tipo e nello stesso ordine
  • Possiamo modificare i valori di default dei parametri

Non sono ammesse altre modifiche alla dichiarazione dei metodi, ActionScript 3 è molto rigoroso nei controlli e richiederà pertanto che il metodo restituisca un valore del tipo specificato o che non venga restituito alcun valore (void).

Note :

  • Una classe può estendere solo un'altra classe (ereditarietà)
  • Una classe può implementare più di una interfaccia
  • Un'interfaccia può estendere più interfacce (ereditarietà multipla)
  • Un'interfaccia non può implementare un'altra interfaccia

19. Classi Dinamiche e Finali

Classi Dinamiche

Con ActionScript 3 viene introdotto un nuovo concetto applicabile alle nostre classi, che potranno essere sigillate (sealed) oppure dinamiche (dynamic).

Il valore predefinito per tutte le classi è sealed, indicando al compilatore che la classe in esame è sigillata, ossia non vi sarà la possibilità di aggiungere attributi e metodi alla classe in fase di run-time.

Dichiarando invece una classe mediante la direttiva dynamic, avremo la possibilità di aggiungere dei membri ad essa anche al di fuori della sua dichiarazione o in un altro file ActionScript.

Classe Sealed

package
{
	public class MiaClasseSealed
	{ }
}

Classe Dynamic

package
{
	dynamic public class MiaClasseDynamic
	{ }
}

Come potete notare dal codice soprastante, non è necessaria alcuna direttiva per dichiarare una classe come sealed, al contrario per dichiararla dinamica ho anteposto alla dichiarazione della classe il comando dynamic.

Di seguito la classe documento HelloWorld che dichiara ed utilizza due istanze delle classi sopra dichiarate :

package
{
	import MiaClasseSealed;
	import MiaClasseDynamic;

	public class HelloWorld extends MovieClip
	{
		public function HelloWorld():void
		{
			var mySealed:MiaClasseSealed = new MiaClasseSealed();
			var myDynamic:MiaClasseDynamic = new MiaClasseDynamic();

			mySealed.attributo = 10; // Errore
			myDynamic.attributo = 10; // Tutto OK!
		}
	}
}

L'istruzione "mySealed.attributo = 10;" genera il seguente errore poichè la classe è sigillata e non ammette attributi o metodi che non siano stati esplicitamente dichiarati nel corpo della classe :

Accesso a una proprietà non definita attributo mediante un riferimento con tipo statico MiaClasseSealed.


Classi Finali

Abbiamo già dato un'occhiata alle classi finali nel capitolo Costanti ed Enumerazioni, dichiarando quest'ultime come classi finali, a causa del mancato supporto esplicito in ActionScript 3 per questo tipo di costrutti.

Dichiarando una classe come finale, diciamo al compilatore che nessuna classe può ereditare da essa, provocando quindi un errore in fase di compilazione qualora si tentasse di creare un'istanza di una classe che eredita da una classe finale.

Classe base finale

package
{
	public final class ClasseFinale
	{
		// Corpo della classe
	}
}

Classe derivata

package
{
	import ClasseFinale;

	public class Oggetto extends ClasseFinale
	{
		public function Oggetto():void
		{
			trace("Costruttore");
		}
	}
	
}

Provando a creare un'istanza della classe Oggetto otterrete il seguente errore :
La classe base è final.

18. Ereditarietà

L'ereditarietà è uno dei concetti base su cui poggia uno dei paradigmi di programmazione più usati dalle applicazioni moderne, la programmazione orientata agli oggetti, che abbiamo già avuto modo di conoscere nel precedente capitolo sulle classi : Classi

Questo metodo di programmazione ci consente di sfruttare appieno le potenzialità di questo paradigma per i seguenti motivi :

ActionScript 3 ci consente di creare una sottoclasse derivata da un'altra mediante la direttiva extends, come mostrato nell'esempio seguente.

Unita.as - Classe Base

package
{
	public class Unita
	{
		protected var attacco:uint;
		protected var difesa:uint;
		protected var distanza:uint;
		protected var velocita:uint;

		public function Unita():void
		{ /* ... Costruttore ... */ }
	}
}

Fante.as - Classe Derivata

package
{
	import Unita;

	public class Fante extends Unita // "Fante" eredita da "Unita"

	{
		protected var puntiferita:uint;

		public function Fante():void
		{
			this.attacco = 10;
			this.difesa = 20;
			this.distanza = 1;
			this.velocita = 5;

			this.puntiferita = 100;
		}
	}
}

Come possiamo notare dall'esempio soprastante, la classe "Fante" eredita i quattro attributi protected dichiarati nella classe "Unita".
Nota : protected è un modificatore di accesso che restringe la visibilità dei membri alla sola classe base ed alle sue derivate.

Nell'esempio soprastante, abbiamo esteso il codice senza effettuare alcuna modifica alla classe esistente "Unita", ma semplicemente creando una classe specializzata per rappresentare il nuovo tipo di dato "Fante" con un attributo aggiuntivo.

Un altro strumento che l'ereditarietà ci mette a disposizione è la possibilità di ridefinire alcuni elementi delle classi base.

Molti linguaggi ci consentono di ridefinire sia gli attributi che i metodi di una classe, mentre ActionScript 3 ci permette di ridefinire solo i metodi, lasciando tutti gli attributi e le costanti delle classe base (var e const) come proprie di quest'ultima.

E' comunque possibile ridefinire gli attributi attraverso la definizione dei metodi speciali get e set che abbiamo visto nel capitolo precedente.


Ridefinizione dei Metodi

Per ridefinire un metodo in ActionScript 3, si usa la direttiva override seguita dalla dichiarazione del metodo, che dovrà rispecchiare esattamente la struttura del metodo base o il compilatore restituirà il messaggio "Override incompatibile".

Questo significa che se il metodo base prende un determinato numero di parametri di un tipo stabilito, il metodo che andrà a ridefinirlo dovrà prendere gli stessi parametri dello stesso tipo.

Un metodo sarà ridefinibile solo se dichiarato pubblico, protetto o interno.
Se dichiarato privato sarà comunque possibile definire un metodo con lo stesso nome nella classe derivata, in quanto il metodo della classe base non sarà visibile all'esterno (private), e procederemo quindi senza la direttiva override.

Di seguito un semplice esempio dove ridefiniremo il metodo "muovi()" :

Unita.as - Classe Base

package
{
	public class Unita
	{
		protected var attacco:int;
		protected var difesa:int;
		protected var distanza:int;
		protected var velocita:int;

		public function Unita():void
		{ /* ... Costruttore ... */ }

		public function muovi(passi:uint):void
		{
			/* Sposta l'unita di N passi */
			trace("Chiamata a 'Unita.muovi()'");
		}
	}
}

Fante.as - Classe Derivata

package
{
	import Unita;
	
	public class Fante extends Unita
	{
		public function Fante():void
		{
			this.attacco = 10;
			this.difesa = 20;
			this.distanza = 1;
			this.velocita = 5;
		}

		override public function muovi(passi:uint):void
		{
			trace("Chiamata a 'Fante.muovi()'");
		}
	}
}

HelloWorld.as - Classe documento

package
{
	import Fante;
	import flash.display.MovieClip;

	public class HelloWorld extends MovieClip
	{
		public function HelloWorld():void
		{
			var soldato:Fante = new Fante();
			soldato.muovi(10); // Output : Chiamata a 'Fante.muovi()'
		}
	}
}

Se non avessimo effettuato un override nella classe Fante, la chiamata al metodo "muovi()" avrebbe provocato il seguente output :
Chiamata a 'Unita.muovi()'


Metodo super

Potrebbe capitare durante l'implementazione di un override, di non voler riscrivere completamente il metodo base ma di volergli solo aggiungere qualche funzionalità.

In questo caso ci sarà di aiuto lo speciale metodo super, attraverso cui ci sarà possibile accedere direttamente alle proprietà (attributi e metodi) della classe base, da una sua derivata.

Nel prossimo esempio riscriverò solo la classe Fante, lasciando i file Unita.as e HelloWorld.as invariati :

Fante.as - Classe Derivata

package
{
	import Unita;
	
	public class Fante extends Unita
	{
		public function Fante():void
		{
			this.attacco = 10;
			this.difesa = 20;
			this.distanza = 1;
			this.velocita = 5;
		}

		override public function muovi(passi:uint):void
		{
			super.muovi(passi * this.velocita);
			trace("Chiamata a 'Fante.muovi()'");
		}
	}
}

Richiamando la classe documento HelloWorld si ottiene il seguente output :
Chiamata a 'Unita.muovi()'
Chiamata a 'Fante.muovi()'

Nota : vengono ereditati tutti i membri che non sono private e statici.

17. Metodi get e set

I metodi get e set, sono dei metodi particolari, poichè ci consentono di definire un attributo su cui avremo il pieno controllo per ogni operazione di scrittura e lettura.

Non è obbligatorio definire entrambi i metodi, ActionScript 3 ci permette di definire solo il metodo di cui abbiamo bisogno, consentendo ovviamente solo operazioni di lettura qualora avessimo definito get, o solo operazioni di scrittura qualora avessimo definito solo set.

Per dichiarare uno di questi metodi, è sufficiente anticipare il nome del metodo con una delle due direttive get o set.


Esempio

Supponiamo di voler disporre di un attributo nella nostra classe con il compito di memorizzare solo numeri interi maggiori o uguali a zero.

Dovremmo ricordarci di effettuare un casting al tipo di dato int ogni volta che assegniamo un valore a questo attributo, controllando inoltre che il valore ricevuto sia maggiore maggiore o uguale a zero, scrivendo più righe di codice che saranno anche più soggette ad errori, poichè è facile dimenticare di effettuare una conversione esplicita quando ci sono parecchie assegnazioni da fare.

Vedremo ora un esempio pratico dove una classe "MiaClasse" definirà questi due metodi speciali per risolvere il problema pocanzi esposto.

MiaClasse.as

package
{
	public class MiaClasse
	{
		private var numero:int;

		public function MiaClasse():void
		{
			this.numero = 0;
		}
		
		public function get mionumero():int
		{ return this.numero; }

		public function set mionumero(num:*):void
		{
			// Converte esplicitamente il nuovo valore al tipo int
			num = int(num);

			/* Se num è maggiore o uguale a zero assegna il valore
			all'attributo numero, altrimenti assegnalo a zero */
			this.numero = (num >= 0) ? num : 0;
		}
	}
}

I due metodi dell'esempio soprastante ci consentiranno di gestire uno pseudo-attributo con il nome di mionumero.

Nota che il nome del vero attributo che memorizzerà il valore (numero), è privato per evitare incidenti, in modo che sia possibile gestirlo solo attraverso i metodi get e set.

Inoltre il suo nome (numero) è diverso da quello dello pseudo-attributo dichiarato attraverso get e set, per evitare di ricevere errori in fase di compilazione su un conflitto per la duplicazioni dei nomi.

Di seguito un esempio con la classe documento HelloWorld.

HelloWorld.as

package
{
	import MiaClasse;
	import flash.display.MovieClip;

	public class HelloWorld extends MovieClip
	{
		public function HelloWorld():void
		{
			var istanza:MiaClasse = new MiaClasse();

			istanza.mionumero = "56.7";
			trace(istanza.mionumero); // Output : 56

			istanza.mionumero = true;
			trace(istanza.mionumero); // Output : 1

			istanza.mionumero = 77.4;
			trace(istanza.mionumero); // Output : 77

			istanza.mionumero = 14;
			trace(istanza.mionumero); // Output : 14
		}
	}
}

16. Costanti ed Enumerazioni


Costanti

Possiamo definire le costanti come delle variabili di sola lettura, poichè una volta dichiarate e definite, non sarà possibile modificarne il valore in fase di run-time.

Nella dichiarazione di una costante è possibile omettere la parola chiave var, utilizzando solamente la direttiva const in questo modo :

const MAX_UTENTI:int = 10;

Nella stessa maniera è possibile dichiarare delle costanti come attributi di una classe :

package
{
	public class ChatRoom
	{
		public const MAX_UTENTI:int = 10;
		public var nome_stanza:String;

		// ...
	}
}

Le costanti possono essere anche statiche, in modo che siano appartenenti solo alla classe e quindi non utilizzabili tramite istanze.


Enumerazioni

Le enumerazioni sono un buon metodo per racchiudere sotto un'unica classe, un gruppo di nomi a cui corrispondono dei determinati valori che possiamo conoscere a priori.

Le enumerazioni in ActionScript 3 non sono direttamente supportate come in C++ o in C#, ma è comunque possibile usufruire di questa metodologia di programmazione dichiarando una classe con delle costanti pubbliche e statiche, come avviene in PHP 5.

Vediamo un esempio :

package
{
	public final class Colori
	{
		public static const Rosso:String = "#FF0000";
		public static const Verde:String = "#00FF00";
		public static const Blu:String = "#0000FF";
		// ...
	}
}

Potete anche creare un'istanza della classe "Colori", ma questa sarà inutile in quanto non possiederà alcun metodo o attributo utilizzabile, semplicemente ActionScript 3 non esegue alcun controllo di questo tipo.

HelloWorld.as

package
{
	import Colori;
	import flash.display.MovieClip;

	public class HelloWorld extends MovieClip
	{
		public function HelloWorld():void
		{
			trace("Il valore esadecimale del colore rosso è " + Colori.Rosso);
		}
	}
}

L'esempio produce il seguente output :
Il valore esadecimale del colore rosso è #FF0000

15. Membri statici


Attributi statici

Fino ad ora abbiamo visto che per utilizzare gli attributi e i metodi di una classe, è necessario creare un'istanza di tale classe, che viene poi usata per richiamare i membri attraverso l'operatore "." (punto).

ActionScript ci consente la gestione di tali attributi e metodi, senza alcuna necessità di allocare un'istanza, dichiarando questi membri come statici, ossia appartenenti solo alla classe e non alle istanze di questa.

Un attributo statico e un metodo statico possono essere paragonati rispettivamente ad una variabile e ad una funzione tradizionali, soggetti però ai vincoli di visibilità imposti dalla classe di appartenenza.

Per accedervi perciò è necessario specificare il nome della classe, seguito dall'operatore di selezione "." (punto).

Questo metodo può tornare utile quando, ad esempio, ci è necessario condividere un valore con tutte le istanze di una classe, e a questo proposito vedremo nell'esempio che segue, una classe DatiLogin che avrà due attributi statici che verranno utilizzati da tutte le istanze della suddetta classe, per verificare che il nome utente e la password usati abbiano una lunghezza minore o uguale alla massima consentita.

DatiLogin.as

package
{
	public class DatiLogin
	{
		// Lunghezza massima per il nome utente di 15 caratteri
		static public var nome_max_length:int = 15;
		// Lunghezza massima per la password utente di 20 caratteri
		static public var pass_max_length:int = 20;

		private var nome_utente:String;
		private var pass_utente:String;

		public function DatiLogin(nome:String, pass:String):void
		{
			if (nome.length <= DatiLogin.nome_max_length) this.nome_utente = nome;
			else throw new Error("Nome utente (" + nome + ") troppo lungo!");

			if (pass.length <= DatiLogin.pass_max_length) this.pass_utente = pass;
			else throw new Error("Password utente (" + nome + ") troppo lunga!");
		}

		public function connetti():void
		{
			/* Si connette ad un server utilizzando
			nome_utente e pass_utente per autenticarsi */
		}
	}
}

La classe dell'esempio soprastante è stata scritta per uno scopo puramente didattico.

Di seguito un esempio che mostra un uso ipotetico della classe DatiLogin.

HelloWorld.as

package
{
	import DatiLogin;
	import flash.display.MovieClip;

	public class HelloWorld extends MovieClip
	{
		public function HelloWorld():void
		{
			/* Reimpostiamo la lunghezza massima
			per il nome utente fino a 20 caratteri */
			DatiLogin.nome_max_length = 4;

			var loginSito1:DatiLogin = new DatiLogin("root", "cicciobello"); // Ok
			var loginSito2:DatiLogin = new DatiLogin("admin", "pippopluto"); // Errore
		}
	}
}

L'inizializzazione dell'istanza loginSito2 provoca un errore, perchè il nome utente "admin" è di 5 caratteri, mentre il massimo consentito è stato impostato a 4 attraverso la variabile statica "nome_max_length".

Ricordate che non potrete accedere ai membri statici attraverso le istanze, e neppure attraverso this dall'interno della classe, ma solo specificando il nome della classe come abbiamo visto nell'esempio precedente.

var login:DatiLogin = new DatiLogin("root", "password");

login.nome_max_length = 15; // Errore, operazione non consentita
DatiLogin.nome_max_length = 15; // Tutto Ok!

Metodi statici

Come ho anticipiato all'inizio, i metodi statici sono come delle funzioni tradizionali, ma vincolate dalle regole di visibilità ed accessibilità imposte dalla classe a cui appartengono.

Può essere necessario implementare un metodo statico in una classe, per fornire ad esempio un pseudo-costruttore che invece di inizializzare un nuovo oggetto, restituisce un'istanza di questa classe inizializzata a partire da un file XML esterno.

Segue una bozza di una classe d'esempio "Salvataggio", che disporrà di un metodo statico per caricare i salvataggi da un file esterno in formato XML.


Salvataggio.as

package
{
	public class Salvataggio
	{
		var partita:*;
		var data:Date;
		var giocatore:String;

		public function Salvataggio():void
		{
			// ...
		}

		static public function caricaDaXML(nome_file:String):Salvataggio
		{
			var save:Salvataggio = new Salvataggio();

			/* Apre e legge un file XML con i dati di gioco ed
			inizializza un'istanza di "Salvataggio" con questi dati */

			return save;
		}
	}
}

HelloWorld.as

package
{
	import Salvataggio;
	import flash.display.MovieClip;

	public class HelloWorld extends MovieClip
	{
		public function HelloWorld():void
		{
			var save:Salvataggio = Salvataggio.caricaDaXML("mario_01_save.xml");
		}
	}
}

Infine, i membri statici, non vengono eriditati dalle sottoclassi.

Condividi contenuti