Rubriques avancées

Historique de la prise en charge de la programmation orientée objets en ActionScript

ActionScript 3.0 est une évolution des versions antérieures d’ActionScript. C’est pourquoi il peut être utile de comprendre l’évolution du modèle d’objet en ActionScript. ActionScript était à l’origine un simple mécanisme de script pour les premières versions de Flash Professional. Les programmeurs ont alors commencé à développer des applications de plus en plus complexes avec ActionScript. En réponse aux besoins de ces programmeurs, le langage de chaque nouvelle version a connu des ajouts destinés à faciliter la création d’applications complexes.

ActionScript 1.0

ActionScript 1.0 est le langage utilisé dans les versions 6 et antérieures de Flash Player. Même à ce stade précoce de développement, le modèle d’objet d’ActionScript était basé sur le concept de l’objet comme type fondamental de données. Un objet ActionScript est un type de données composite doté d’un groupe de propriétés. Dans le contexte d’un modèle d’objet, le terme propriétés désigne tout ce qui est affecté à un objet (variables, fonctions ou méthodes).

Bien que cette première génération d’ActionScript ne prenne pas en charge la définition de classes avec le mot-clé class, elle permet de définir une classe à l’aide d’un type spécial d’objet appelé objet prototype. Au lieu d’utiliser un mot-clé class pour créer une définition de classe abstraite qui sera ensuite instanciée en objets concrets (à l’instar des langages reposant sur des classes, comme Java ou C++), les langages basés sur le prototypage, tel ActionScript 1.0, utilisent un objet existant comme modèle (ou prototype) d’autres objets. Alors que les objets des langages basés sur la notion de classes peuvent pointer sur une classe qui leur sert de modèle, les objets des langages basés sur la notion de prototypes pointent sur un autre objet, leur prototype, qui leur sert de modèle.

Pour créer une classe en ActionScript 1.0, il est nécessaire de définir une fonction constructeur pour cette classe. En ActionScript, les fonctions sont des objets réels et non pas de simples définitions abstraites. La fonction constructeur sert alors d’objet prototype pour les occurrences de cette classe. Le code suivant crée une classe appelée Shape et définit une propriété appelée visible qui a la valeur true par défaut.

// base class 
function Shape() {} 
// Create a property named visible. 
Shape.prototype.visible = true;

Cette fonction constructeur définit une classe Shape qui va être instanciée à l’aide de l’opérateur new, comme suit :

myShape = new Shape();

De même que l’objet de la fonction constructeur Shape() sert de prototype pour les occurrences de la classe Shape, il peut également servir pour les sous-classes de Shape, c’est-à-dire d’autres classes qui prolongent la classe Shape.

La création d’une classe qui est une sous-classe de la classe Shape est un processus en deux étapes. D’abord, créer la classe en définissant une fonction constructeur pour cette classe, comme suit :

// child class 
function Circle(id, radius) 
{ 
this.id = id; 
this.radius = radius; 
}

Ensuite, utiliser l’opérateur new pour déclarer que la classe Shape est le prototype de la classe Circle. Par défaut, toute nouvelle classe créée utilise la classe Object comme prototype, si bien que Circle.prototype contient alors un objet générique (une occurrence de la classe Object). Pour spécifier que le prototype de Circle est Shape et non pas Object, utilisez le code suivant pour changer la valeur de Circle.prototype afin qu’elle contienne un objet Shape et non plus un objet générique :

// Make Circle a subclass of Shape. 
Circle.prototype = new Shape();

La classe Shape et la classe Circle sont maintenant liées par une relation d’héritage communément appelée chaînage du prototype. Le schéma illustre la relation au sein d’un chaînage de prototype :

La classe de base, à la fin de chaque chaînage de prototype, est la classe Object. La classe Object contient une propriété statique appelée Object.prototype qui pointe sur l’objet prototype de base pour tous les objets créés en ActionScript 1.0. Dans l’exemple de chaînage de prototype, l’objet suivant est l’objet Shape. En effet, la propriété Shape.prototype n’a jamais été explicitement définie, si bien qu’elle contient encore un objet générique (une occurrence de la classe Object). Le lien final de ce chaînage est la classe Circle qui est liée à son prototype, la classe Shape (la propriété Circle.prototype contient un objet Shape).

Si vous créez une occurrence de la classe Circle, comme dans l’exemple ci-dessous, l’occurrence hérite du chaînage de prototype de la classe Circle :

// Create an instance of the Circle class. 
myCircle = new Circle();

Pour rappel, l’exemple contient une propriété appelée visible, qui est membre de la classe Shape. La propriété visible n’existe pas comme partie de l’objetmyCircle, mais uniquement comme membre de l’objet Shape ; et pourtant la ligne de code suivante produit la valeur true :

trace(myCircle.visible); // output: true

En remontant le chaînage du prototype, le moteur d’exécution est en mesure de vérifier que l’objet myCircle hérite de la propriété visible. Lorsque ce code est exécuté, le moteur d’exécution recherche d’abord dans les propriétés de l’objet myCircle une propriété appelée visible, mais ne la trouve pas. Il examine alors l’objet Circle.prototype, mais ne trouve toujours pas la propriété visible. En continuant à remonter le chaînage du prototype, il finit par trouver la propriété visible définie dans l’objet Shape.prototype et affiche sa valeur.

Par souci de simplicité, la présente section omet un grand nombre de détails et de subtilités du chaînage de prototype, puisqu’il s’agit simplement de vous aider à comprendre le modèle d’objet en ActionScript 3.0.

ActionScript 2.0

Avec ActionScript 2.0 ont été introduits de nouveaux mots-clés tels que class, extends, public et private qui permettaient de définir des classes selon une méthode bien connue de toute personne ayant travaillé avec les langages basés sur des classes, comme Java et C++. Il est important de comprendre que le mécanisme sous-jacent d’héritage n’a pas changé entre ActionScript 1.0 et ActionScript 2.0. Le passage à ActionScript 2.0 a consisté simplement à ajouter une nouvelle syntaxe pour la définition des classes. Le chaînage du prototype fonctionne de la même façon dans ces deux versions du langage.

La nouvelle syntaxe introduite par ActionScript 2.0 est représentée dans l’exemple ci-dessous. Elle permet de définir des classes d’une façon que la plupart des programmeurs considèrent comme plus intuitive :

// base class 
class Shape 
{ 
var visible:Boolean = true; 
}

Vous pouvez remarquer qu’ActionScript 2.0 a également introduit les annotations de type destinées à une vérification des types à la compilation. Elles permettent de déclarer que la propriété visible de l’exemple précédent ne doit contenir qu’une valeur booléenne. Le nouveau mot-clé extends simplifie lui aussi le processus de création d’une sous-classe. Dans l’exemple suivant, ce qui nécessitait deux étapes en ActionScript 1.0 est accompli en une seule, à l’aide du mot-clé extends :

// child class 
class Circle extends Shape 
{ 
    var id:Number; 
    var radius:Number; 
    function Circle(id, radius) 
    { 
        this.id = id; 
        this.radius = radius; 
        } 
}

Le constructeur est maintenant déclaré dans le cadre de la définition de classe, et les propriétés id et radius de la classe doivent elles aussi être déclarées explicitement.

ActionScript 2.0 a également ajouté la prise en charge de la définition des interfaces, qui permet de rendre plus perfectionnés des programmes orientés objet, à l’aide de protocoles formellement définis pour la communication entre les objets.

Objet de classe en ActionScript 3.0

Un paradigme courant en programmation orientée objets, en particulier avec Java et C++, fait appel à des classes pour définir des types d’objets. Les langages de programmation qui adoptent ce paradigme ont aussi tendance à utiliser des classes pour construire des occurrences du type de données défini par la classe. ActionScript utilise des classes pour ces deux raisons, mais ses origines de langage basé sur des prototypes lui confèrent une caractéristique intéressante. Pour chaque définition de classe, ActionScript crée un objet de classe spécial qui autorise le partage du comportement et de l’état. Toutefois, pour de nombreux programmeurs en ActionScript, cette distinction n’aura aucune implication pratique sur le codage. En effet, ActionScript 3.0 est conçu de telle sorte qu’il est possible de créer des applications perfectionnées orientées objet sans utiliser, et sans même comprendre, ces objets de classe spéciaux.

Le schéma suivant montre la structure d’un objet de classe représentant une classe simple appelée A, définie par l’instruction class A.

Chaque rectangle du schéma représente un objet. Chaque objet du schéma est marqué d’un caractère en indice pour signaler qu’il appartient à la classe A. L’objet de classe (CA) contient des références à un certain nombre d’autres objets importants. L’objet traits des occurrences (TA) enregistre les propriétés des occurrences qui sont définies dans une définition de classe. Un objet traits de la classe (TCA) représente le type interne de la classe et enregistre les propriétés statiques définies par la classe (le caractère C en indice signifie « classe »). L’objet prototype (PA) désigne toujours l’objet de classe auquel il était associé à l’origine par la propriété constructor.

Objet traits

L’objet traits est une nouveauté d’ActionScript 3.0, implémentée pour des raisons de performances. Dans les versions précédentes d’ActionScript, la recherche d’un nom pouvait demander beaucoup de temps pendant que Flash Player remontait le chaînage du prototype. En ActionScript 3.0, la recherche d’un nom est beaucoup plus rapide et efficace, car les propriétés héritées sont copiées des superclasses dans l’objet traits des sous-classes.

L’objet traits n’est pas directement accessible par code, mais sa présence se manifeste par l’amélioration des performances et de l’utilisation de la mémoire. L’objet traits fournit à la machine virtuelle AVM2 des informations détaillées sur la disposition et le contenu d’une classe. Ces informations permettent à AVM2 de réduire nettement le temps d’exécution, car elle peut fréquemment générer des instructions machine directes pour accéder à des propriétés ou appeler des méthodes directement, sans effectuer au préalable une longue recherche de nom.

Grâce à l’objet traits, l’espace occupé en mémoire par un objet peut être nettement moins importante qu’avec les versions antérieures d’ActionScript. Par exemple, si une classe est scellée (c’est-à-dire si elle n’est pas déclarée comme dynamique), une occurrence de la classe ne nécessite pas de recherche par calcul d’adresse pour les propriétés ajoutées dynamiquement ; il lui suffit d’un pointeur sur l’objet traits et de quelques emplacements pour les propriétés fixes définies dans la classe. En conséquence, pour un objet qui nécessitait 100 octets en mémoire avec ActionScript 2.0, 20 suffiront avec ActionScript 3.0.

Remarque : l’objet traits est un élément d’implémentation interne et il n’est pas garanti qu’il ne changera pas, ou même qu’il ne disparaîtra pas, dans les futures versions d’ActionScript.

Objet prototype

En ActionScript, chaque objet de classe possède une propriété appelée prototype, qui est une référence à l’objet prototype de cette classe. L’objet prototype est un héritage des premières versions d’ActionScript, basées sur des prototypes. Pour plus d’informations, voir Historique de la prise en charge de la programmation orientée objets en ActionScript.

La propriété prototype est en lecture seule et ne peut donc pas être modifiée pour pointer sur d’autres objets. A l’inverse, dans les anciennes versions d’ActionScript, la propriété prototype pouvait être réaffectée pour pointer sur une autre classe. Bien que la propriété prototype soit en lecture seule, ce n’est pas le cas de l’objet prototype à laquelle elle fait référence. En d’autres termes, de nouvelles propriétés peuvent être ajoutées à l’objet prototype. Les propriétés ajoutées à l’objet prototype sont partagées avec toutes les autres occurrences de la classe.

Le chaînage de prototype, qui était le seul mécanisme d’héritage des versions antérieures d’ActionScript, n’a plus qu’un rôle secondaire en ActionScript 3.0. Le principal mécanisme d’héritage, l’héritage des propriétés fixes, est géré de façon interne par l’objet traits. Une propriété fixe est une variable ou une méthode définie dans le cadre d’une définition de classe. L’héritage des propriétés fixes est également appelé héritage des classes, car c’est le mécanisme d’héritage qui est associé aux mots-clés class, extends et override.

Le chaînage de prototype représente un autre mécanisme d’héritage qui est plus dynamique que l’héritage des propriétés fixes. Il est possible d’ajouter des propriétés à l’objet prototype d’une classe, non seulement dans le cadre de la définition de classe, mais aussi lors de l’exécution, par la propriété prototype de l’objet de classe. Notez toutefois que si le compilateur est en mode strict, il peut être impossible d’accéder aux propriétés ajoutées à un objet prototype, sauf si vous déclarez une classe avec le mot-clé dynamic.

La classe Object est un bon exemple d’une classe dont plusieurs propriétés sont attachées à l’objet prototype. Les méthodes toString() et valueOf() de la classe Object sont en fait des fonctions affectées à des propriétés de l’objet prototype de la classe Object. Voici un exemple de l’aspect théorique de la déclaration de ces méthodes (l’implémentation réelle est légèrement différente en raison des détails pratiques d’implémentation) :

public dynamic class Object 
{ 
    prototype.toString = function() 
    { 
        // statements 
    }; 
    prototype.valueOf = function() 
    { 
        // statements 
    }; 
}

Comme nous l’avons déjà mentionné, il est possible d’attacher une propriété à l’objet prototype d’une classe en dehors de la définition de cette classe. Par exemple, la méthodetoString() peut également être définie hors de la définition de la classe Object, comme suit :

Object.prototype.toString = function() 
{ 
    // statements 
};

Toutefois, contrairement à l’héritage des propriétés fixes, l’héritage du prototype ne nécessite pas le mot-clé override pour redéfinir une méthode de sous-classe. Par exemple, pour redéfinir la méthode valueOf() dans une sous-classe de la classe Object, trois options sont possibles. Premièrement, vous pouvez définir une méthode valueOf() dans l’objet prototype de la sous-classe, à l’intérieur de la définition de classe. Le code suivant crée une sous-classe d’Object appelée Foo et redéfinit la méthode valueOf() dans la définition de classe de l’objet prototype de Foo. Chaque classe héritant de la classe Object, il n’est pas nécessaire d’utiliser le mot-clé extends.

dynamic class Foo 
{ 
    prototype.valueOf = function() 
    { 
        return "Instance of Foo"; 
    }; 
}

Deuxièmement, vous pouvez définir une méthode valueOf() dans l’objet prototype de Foo en-dehors de la définition de classe, comme le montre le code ci-dessous :

Foo.prototype.valueOf = function() 
{ 
    return "Instance of Foo"; 
};

Troisièmement, vous pouvez définir une propriété fixe appelée valueOf() dans la classe Foo. Cette technique diffère des précédentes dans la mesure où elle mêle l’héritage des propriétés fixes à l’héritage du prototype. Pour redéfinir valueOf() à partir d’une sous-classe de Foo, il est nécessaire d’utiliser le mot-clé override. Le code ci-dessous montre valueOf() définie comme propriété fixe dans Foo :

class Foo 
{ 
    function valueOf():String 
    { 
        return "Instance of Foo"; 
    } 
}

Espace de noms d’ActionScript 3

L’existence de deux mécanismes d’héritage séparés, l’héritage des propriétés fixes et l’héritage du prototype, provoque un problème intéressant de compatibilité par rapport aux propriétés et méthodes des classe de base. La compatibilité avec la spécification du langage ECMAScript sur laquelle est basé ActionScript nécessite l’utilisation de l’héritage de prototype, si bien que les propriétés et les méthodes d’une classe de base sont définies dans l’objet prototype de cette classe. Mais par ailleurs, la compatibilité avec ActionScript 3.0 nécessite l’utilisation de l’héritage des propriétés fixes, si bien que les propriétés et méthodes d’une classe de base sont spécifiées dans la définition de classe, à l’aide des mots-clés const, var et function. De plus, l’utilisation des propriétés fixes plutôt que les versions du prototype est susceptible de permettre une nette amélioration des performances à l’exécution.

Pour résoudre ce problème, ActionScript 3.0 utilise les deux mécanismes d’héritage (propriétés fixes et prototype) pour les classes de base. Chaque classe de base contient deux jeux de propriétés et de méthodes. Un jeu est défini dans l’objet prototype pour assurer la compatibilité avec les spécifications d’ECMAScript et l’autre est défini avec les propriétés fixes et l’espace de noms d’AS3,0, pour assurer la compatibilité avec ActionScript 3.0.

L’espace de noms d’AS3 représente un mécanisme fort pratique pour choisir entre les deux jeux de propriétés et de méthodes. Si vous n’utilisez pas l’espace de noms d’AS3, une occurrence d’une classe de base hérite des propriétés et des méthodes définies dans l’objet prototype de cette classe de base. Si par contre vous utilisez l’espace de noms d’AS3, une occurrence d’une classe de base hérite des versions d’AS3, car les propriétés fixes sont toujours préférées aux propriétés du prototype. En d’autres termes, lorsqu’une propriété fixe est disponible, elle est toujours utilisée à la place d’une propriété du prototype ayant le même nom.

Il est possible d’utiliser sélectivement la version de l’espace de noms d’AS3 d’une propriété ou d’une méthode, en la qualifiant à l’aide de l’espace de noms d’AS3. Par exemple, le code ci-dessous utilise la version AS3 de la méthode Array.pop() :

var nums:Array = new Array(1, 2, 3); 
nums.AS3::pop(); 
trace(nums); // output: 1,2

Vous pouvez aussi faire appel à la directive use namespace afin d’ouvrir l’espace de noms d’AS3 pour toutes les définitions figurant dans un bloc de code. Par exemple, le code ci-dessous utilise la directive use namespace afin d’ouvrir l’espace de noms d’AS3 pour les méthodes pop() et 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 comporte aussi des options de compilation pour chaque jeu de propriétés, ce qui permet d’appliquer l’espace de noms d’AS3 au programme entier. L’option de compilation -as3 représente l’espace de noms d’AS3 et l’option de compilation -es représente l’option d’héritage du prototype (es correspond à ECMAScript). Pour ouvrir l’espace de noms d’AS3 pour tout le programme, définissez l’option de compilation -as3 sur true et l’option de compilation -es sur false. Pour utiliser les versions du prototype, définissez les options de compilation sur les valeurs opposées. Les options de compilation par défaut de Flash Builder et Flash Professional sont -as3 = true et -es = false.

Si vous prévoyez d’étendre l’une des classes de base et de redéfinir des méthodes, vous devez comprendre comment l’espace de noms d’AS3 affecte la façon de déclarer une méthode redéfinie. Si vous utilisez l’espace de noms d’AS3, toute redéfinition d’une méthode d’une classe de base doit également utiliser l’espace de noms d’AS3, ainsi que l’attribut override. Si vous n’utilisez pas l’espace de noms d’AS3 et voulez redéfinir une méthode d’une classe de base dans une sous-classe, vous ne devez utiliser ni l’espace de noms d’AS3 dans cette redéfinition, ni l’attribut override.