Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Introduction aux classes ActionScript

Compatible ActionScript 2. Cliquer pour en savoir plus sur les compatibilités.

Tout d'abord, il faut expliquer les différences entre ActionScript 1 et ActionScript 2 pour comprendre l'intérêt de migrer de l'un à l'autre.

AS2 est radicalement conçu pour la programmation orientée objet, l'AS1 empêchait une réelle architecture POO dans une application Flash. On découvre en AS2 des familiarités avec d’autres langages comme le Java et le C++. En effet, en AS2, de nouvelles clés ont été ajoutées.

  • class – création de class
  • extends – permet l'héritage entre une superclass et sa class fille
  • interface – permet de créer des interfaces à la JavaLike, pour obliger les classes qui l'implémente d'avoir les méthodes déclarées dans celle-ci.
  • private/public – permet d'autoriser ou non l'accès à des propriétés ou méthode d'une class.
  • static – pour influencer toutes les occurrences d'une class à la fois, et le typage de variables.
  • throw/try/catch/finally – pour une gestion des erreurs bien plus fine.
  • :Type – pour une gestion parfaite du typage des variables ( impossible en AS1 )

Nous allons donc voir comment créer une classe, l'utiliser, accéder à ses propriétés, protéger ses propriétés et ses méthodes …

Hierachisation des class

Tout d'abord, je vous conseille d'utiliser un éditeur ActionScript externe à Flash, car les classes doivent être créées dans des fichiers .as, et non dans un .fla. La hiérarchie des dossiers contenant des classes est importante, car elle influe sur leur déclaration (on parle de packages) :

/dossier/MaClass.as donne :

/*Fichier MaClass.as*/
class dossier.MaClass
{
  //contenu de la class
}
/*Sur la timeline d'un .fla*/
//usage
import dossier.MaClass;

Définition d'une class

Bon, il est maintenant temps de passer à la définition d'une classe, son utilisation et les possibilités qu'elle offre.

Une classe a généralement une fonction constructeur. Un constructeur est une méthode portant le nom de la classe qui la contient, elle permet généralement d'intialiser les propriétés et les paramètres.

/*Fichier MaClass.as*/
class dossier.MaClass
{
	public function MaClass ()
	{
		trace ("Nouvelle occurrence de MaClass créée !")
	}
}
/*Sur la timeline d'un .fla*/
import dossier.MaClass;
var i1:MaClass = new MaClass();
var i2:MaClass = new MaClass();

variables d'occurences/de classes

Un aspect intéressant des classes est de pouvoir spécifier des propriétés/variables spécifiques à une occurrence de classe, ou à toutes les occurrences d'une classe, en utilisant le mot-clé static.

class dossier.MaClass
{
	public static var nInstance : Number = 0; // Indentique pour toutes les occurences.
	public var x : Number = 0; // Spécifique à chaque occurence.
	public function MaClass ()
	{
		x = Math.round (Math.random()*10000);
		nInstance ++;
		trace ("Nouvelle instance/occurrence de MaClass")
	}
	public function get instance ():Number
	{
		return nInstance;
	}
}
/*Sur la timeline d'un .fla*/
import dossier.MaClass;
var i1:MaClass = new MaClass();
trace ("Instance i1 : "+i1.instance)
trace ("Identifiant i1 : "+i1.x)
var i2:MaClass = new MaClass();
trace ("Instance i2 : "+i2.instance)
trace ("Identifiant i2 : "+i2.x)
trace ("Instance i1 : "+i1.instance)
trace ("Identifiant i1 : "+i1.x)
// résultats :
/*
Nouvelle instance/occurrence de MaClass
Instance i1 : 1
Identifiant i1 : 1303
Nouvelle instance/occurrence de MaClass
Instance i2 : 2
Identifiant i2 : 4400
Instance i1 : 2
Identifiant i1 : 1303
*/

On remarquera que chaque occurrence de MaClass a une variable qui lui est propre x, mais que les 2 occurrences de MaClass ont en commun la variable nInstance, à laquelle on ne peut accéder directement. Celle-ci est, en effet, une propriété de classe, et non d'occurrence.

Il existe une autre méthode pour accéder aux propriétés de classe, ne pas passer par une occurrence de celle-ci, mais par la class elle-même :

/*Sur la timeline d'un .fla*/
import dossier.MaClass;
trace ("Récupération via la class : "+MaClass.nInstance)
 
var i1:MaClass = new MaClass();
trace ("Instance i1 : "+i1.instance)
trace ("Récupération via la class : "+MaClass.nInstance)
trace ("Identifiant i1 : "+i1.x)
MaClass.nInstance = 0;
trace ("Instance i1 : "+i1.instance)
trace ("Récupération via la class : "+MaClass.nInstance)
/*Résultats : */
/*
Récupération via la class : 0
Nouvelle instance/occurrence de MaClass
Instance i1 : 1
Récupération via la class : 1
Identifiant i1 : 4478
Instance i1 : 0
Récupération via la class : 0
*/

Vous commencez à saisir l'intérêt de la chose ?, vous n'avez encore rien vu.

Setter/Getter

Dans l'exemple précédent, nous avons vu que une méthode particulière, function set, elle est combinable avec function get. Ces setters/getters permettent d'accéder à des variables privées, qui ne permettent pas d'être modifiées ou lues directement :

class dossier.MaClass
{
	private var sPrive : String;
	public var sPublic : String;
	public function MaClass ()
	{
		sPrive = "string privé"
		sPublic = "string publique"
	}
	public function set prive (s:String)
	{
		sPrive = s;
	}
	public function get prive ():String
	{
		return sPrive;
	}
}
/*Sur la timeline d'un fla*/
var i1:MaClass = new MaClass();
trace ("lecture variable sPublic : "+i1.sPublic)
i1.sPublic = "un autre string public"
trace ("lecture variable sPublic après écriture : "+i1.sPublic)
/*Résultats : */
/*
lecture variable sPublic : string publique
lecture variable sPublic après écriture : un autre string public
*/

Voyons les erreurs que provoquerais la compilation si nous tentions quelques petites choses :

Vérification du typage :

// même class qu'au dessus ...
/*Sur la timeline d'un fla*/
var i1:MaClass = new MaClass();
i1.sPublic = 23850 // tentative d'assignation d'un nombre à la place d'un string
/*Résulats :*/
/*
Type mismatch in assignment statement: found Number where String is required.
*/

Tentative d'accès à des variables privées :

// même class qu'au dessus ...
/*Sur la timeline d'un fla*/
var i1:MaClass = new MaClass();
trace (i1.sPrive) // tentative de lecture d'une variable privée
i1.sPrive = "je change la variable" // tentative de modification d'une variable privée
/*Résulats :*/
/*
The member is private and cannot be accessed.
The member is private and cannot be accessed.
*/

Les seuls moyens, d'accéder ou modifier des variables privées sont de passer par des méthodes, ou setters/getters :

/*MaClass.as*/
class MaClass
{
	private var sPrive : String;
	public var sPublic : String;
	public function MaClass ()
	{
		sPrive = "string privé"
		sPublic = "string public"
	}
	public function set prive (s:String)
	{
		sPrive = s;
	}
	public function get prive ():String
	{
		return sPrive;
	}
	public function changePrive (s:String)
	{
		sPrive = s;
	}
	public function recupPrive ():String
	{
		return sPrive;
	}
}
/*Timeline d'un fla*/
var i1:MaClass = new MaClass();
trace ("Get prive : "+i1.prive)
i1.prive = "toto1"; // setter prive
trace ("Get prive : "+i1.prive)
i1.changePrive("toto2") // méthode pour changer sPrive
trace ("Get prive : "+i1.prive)
trace ("Méthode recupPrive : "+i1.recupPrive())
i1.prive = "toto3";
trace ("Méthode recupPrive : "+i1.recupPrive())
/*Résultats : */
/*
Get prive : string privé
Get prive : toto1
Get prive : toto2
Méthode recupPrive : toto2
Méthode recupPrive : toto3
*/

Notez donc la différence dans l'utilisation de méthodes getter/setter, par rapport à des méthodes classiques qui nécessite des arguments ou les ().

Peut-être peut-on y arriver dans le cadre d'une propriété de class et non occurrence ? Reprenons un des exemples précédents et mettons la variable statique en privée.

/*Fichier MaClass.as*/
class MaClass
{
	private static var nInstance : Number = 0;
	public var x : Number = 0;
	public function MaClass ()
	{
		x = Math.round (Math.random()*10000);
		nInstance ++;
		trace ("Nouvelle instance/occurrence de MaClass")
	}
	public function get instance ():Number
	{
		return nInstance;
	}
}
/*Timeline d'un fla*/
import MaClass;
MaClass.nInstance = 45
/*Résultats : */
/*
The member is private and cannot be accessed.
*/

Les variables sont donc effectivement bien protégées.

Héritage et composition

Jusqu'ici, on a juste vu quelques méthodes d'accès, et quelques comportements qui découlent de ces méthodes. Attaquons nous maintenant à ce qu'est vraiment l'utilisation de la programmation orienté objet, que cela soit par de l'héritage, ou de la composition.

L'héritage c'est lorsqu'une class en “étend” une autre, la class qui étend peut accéder aux propriétés et méthodes de la class étendue, elle peut même écraser ces méthodes par les siennes ( surcharge de méthode/propriété ), ou y rajouter des actions, ou avoir des méthodes et propriétés propres. Nous verrons ces différentes possibilités.

Héritage avec des occurrences de class ( pas de propriétés ou méthodes statiques ) :

/*MaClass.as*/
class MaClass
{
	public var sNom:String;
	public function MaClass ()
	{
		trace ("Instanciation de MaClass")
		sNom="genial";
	}
}
/*MaClassX.as*/
import MaClass;
class MaClassX extends MaClass
{
	public function MaClassX ()
	{
		trace ("Instanciation de MaClassX")
		trace ("MaClassX accède à MaClass.sNom : "+sNom)
	}
}
/*Timeline d'un fla*/
import MaClassX;
var i = new MaClassX ();
/* Résultats : */
/*
Instanciation de MaClass
Instanciation de MaClassX
MaClassX accède à MaClass.sNom : genial
*/

Que c'est il donc passé ? Nous avons instancié (création d'une occurrence) MaClassX, mais comme celle-ci est une extension de MaClass, elle a besoin d'avoir les propriétés et méthodes de MaClass, donc on peut récupérer les propriétés de MaClass depuis MaClassX.

L'inverse n'est pas vrai, la classe mère, ou superclasse (MaClass) n'a pas accès aux propriétés et méthodes de la classe enfant (MaClassX). La superclasse étant souvent générique, et des fonctions spécifiques sont déclinées dans les classes étendues.

Avant d'aller plus loin, expliquer les différences entre l'héritage et la composition est important. L'exemple précédent utilise l'héritage. Cependant, il existe une autre manière d'arriver au même résultat : la composition. Ces deux façons de procéder sont souvent en concurrentes, dans certains cas l'une et meilleur que l'autre, et vice-versa. Cependant, la composition est beaucoup plus souple et lisible. La composition n'utilise pas l'extension d'une classe mère par une classe enfant, mais stocke dans la classe enfant une occurrence de la classe mère, rendant l'ensemble plus indépendant.

Reprenons l'exemple précédent, mais en utilisant la composition :

/*MaClass.as*/
class MaClass
{
	public var sNom:String;
	public function MaClass ()
	{
		trace ("Instanciation de MaClass")
		sNom="genial";
	}
}
/*MaClassX.as*/
import MaClass;
class MaClassX
{
	private var x:MaClass;
	public function MaClassX (i:MaClass)
	{
		x = i;
		trace ("Instanciation de MaClassX")
		trace ("MaClassX accède à MaClass.sNom : "+x.sNom)
	}
}
/*Timeline d'un fla*/
import MaClass;
import MaClassX;
var i = new MaClassX (new MaClass());
/ ou :
import MaClassX;
var x = new MaClass()
var i = new MaClassX (x);
/*Résultats :*/
/*
Instanciation de ''MaClass''
Instanciation de ''MaClassX''
''MaClassX'' accède à ''MaClass.sNom'' : genial !
*/

Notez que MaClassX n'a pas de propriété sNom qu'elle hériterait de MaClass, si vous tentiez d'y accéder, vous auriez une erreur de compilation :

import MaClass;
class MaClassX
{
	private var x:MaClass;
	public function MaClassX (i:MaClass)
	{
		x = i;
		trace ("Instanciation de MaClassX")
		trace ("MaClassX accède à MaClass.sNom : "+x.sNom)
		trace ("MaClassX n'a pas de propriété sNom dont elle hérite : "+sNom)
	}
}
/*Timeline d'un fla*/
import MaClassX;
var i = new MaClassX (new MaClass());
/*Résultats :*/
/*
There is no property with the name 'sNom'.
*/

Au lieu de copier une instance de MaClass à l'extérieur MaClassX, vous pourriez aussi la créer directement dans le constructeur de MaClass :

import MaClass;
class MaClassX
{
	private var x:MaClass;
	public function MaClassX ()
	{
		x = new MaClass();
		trace ("Instanciation de MaClassX")
		trace ("MaClassX accède à MaClass.sNom : "+x.sNom)
	}
}
/*Timeline d'un fla*/
import MaClassX;
var i = new MaClassX ();

C'est selon vos besoins, si l'occurrence utilisée n'est pas importante, procédez ainsi.

On respire un petit coup, et maintenant on va compliquer un peu tout cela …

Surcharge de propriétés ou de méthodes

Voyons maintenant l'intérêt des classes étendues avec la surcharge de propriétés et de méthodes ; pour l'instant avec des occurrences de classe et non des classes et propriétés statiques. Dans ces exemples, nous utiliserons l'héritage.

/*MaClass.as*/
class MaClass
{
	public var sNom:String;
	public function MaClass ()
	{
		trace ("MaClass ()")
		sNom="sNom de MaClass";
	}
}
/*MaClassX.as*/
import MaClass;
import MaClass;
class MaClassX extends MaClass
{
	public var sNom:String;
	public function MaClassX ()
	{
		trace ("MaClassX ()")
		trace ("MaClassX.sNom : "+sNom)
		sNom = "sNom de ClassX";
		trace ("MaClassX.sNom : "+sNom)
	}
}
/*Usage : */
import MaClassX;
var i = new MaClassX();
/*Résulats : */
/*
MaClass ()
MaClass.sNom : sNom de MaClass
MaClassX ()
MaClassX.sNom : sNom de MaClass
MaClassX.sNom : sNom de ClassX
*/

Dans ce petit exemple, on a vu que MaClassX.sNom utilisait la valeur de MaClass.sNom, et la stockait. Mais qu'advient-il de MaClass.sNom ? Il est perdu, supprimé après l'instanciation.

/*Autres usages : */
var i = new MaClassX ();
var j = new MaClass ();
/*Résultats : */
/*
MaClass ()
MaClass.sNom : sNom de MaClass
MaClassX ()
MaClassX.sNom : sNom de MaClass
MaClassX.sNom : sNom de ClassX
MaClass ()
MaClass.sNom : sNom de MaClass
*/

Utilisation de super

Jusqu'ici rien de choquant, non ? Etant donné que nous travaillons sur des occurrences de classe chacune est indépendante de l'autre. Il existe un comportement bizarre, à cause des problèmes de compatibilité avec Flash 6. En utilisant le mot-clé super()/super, qui permet de travailler sur la classe mère à partir de la classe fille, nous allons voir quelque chose de bizarre :

/*Modification de MaClassX*/
import MaClass;
class MaClassX extends MaClass
{
	public var sNom:String;
	public function MaClassX ()
	{
 
		trace ("MaClassX ()")
		trace ("MaClassX.sNom : "+sNom)
		sNom = "sNom de ClassX";
		trace ("MaClassX.sNom : "+sNom)
 
	}
	public function getMereNom ()
	{
		trace ("MaClass.sNom ( super ) : "+super.sNom);
	}
}
/*Usage :*/
import MaClassX;
var i = new MaClassX ();
i.getMereNom();
trace (i.sNom)
/* Résultats :*/
/*
MaClass ()
MaClass.sNom : sNom de MaClass
MaClassX ()
MaClassX.sNom : sNom de MaClass
MaClassX.sNom : sNom de ClassX
MaClass.sNom ( super ) : undefined
sNom de ClassX
*/

Si nous initialisons MaClass.sNom, non pas dans le constructeur ou dans une méthode, mais directement dans le corps de MaClass, comme suit :

/*Modification de MaClass */
class MaClass
{
	public var sNom:String="sNom de MaClass";;
	public function MaClass ()
	{
		trace ("MaClass ()")
		trace ("MaClass.sNom : "+sNom)
	}
}
/*Résultats en utilisant l'usage de l'exemple précédent : */
MaClass ()
MaClass.sNom : sNom de MaClass
MaClassX ()
MaClassX.sNom : sNom de MaClass
MaClassX.sNom : sNom de ClassX
MaClass.sNom ( super ) : sNom de MaClass
sNom de ClassX

MaClass.sNom reste accessible. Ce comportement va sûrement changer avec les prochaines versions de Flash, aussi ne l'utilisez pas.

Par contre, c'est très différent lorsque vous utilisez des propriétés de classe (et non d'occurrence de classe) en utilisant l'attribut static, là, tout reste disponible, et c'est normal :

/*MaClass.as*/
class MaClass
{
	public static var sNom:String
	public function MaClass ()
	{
		trace ("MaClass ()")
		sNom ="sNom de MaClass";
		trace ("MaClass.sNom : "+sNom)
	}
}
/*MaClassX.as */
import MaClass;
class MaClassX extends MaClass
{
	public static var sNom:String;
	public function MaClassX ()
	{
 
		trace ("MaClassX ()")
		trace ("MaClassX.sNom 1 : "+sNom)
		sNom = MaClass.sNom
		trace ("MaClassX.sNom 2 : "+sNom)
		sNom = "sNom de ClassX";
		trace ("MaClassX.sNom 3 : "+sNom)
		trace ("MaClass.sNom à partir de MaClassX : "+MaClass.sNom)
	}
}
/*Usage*/
import MaClassX;
var i = new MaClassX ();
/*Résultats :*/
/*
MaClass ()
MaClass.sNom : sNom de MaClass
MaClassX ()
MaClassX.sNom 1 : undefined
MaClassX.sNom 2 : sNom de MaClass
MaClassX.sNom 3 : sNom de ClassX
MaClass.sNom à partir de MaClassX : sNom de MaClass
*/

Donc, on lance une occurrence de MaClassX, mais celle-ci ne va pas récupérer sNom de MaClass, car elle a son propre sNom. Il a l'attribut static qui le définit dans le corps de MaClassX. En plus, MaClass.sNom est toujours disponible, car c'est elle aussi une propriété de classe elle n'est pas écrasée par MaClassX.sNom.



En savoir plus ici.