1 juin 2004

Interfaces et classes virtuelles

Plongé actuellement dans le passionant ouvrage qu'est Design patterns. Catalogue des modèles de conception réutilisables (merci Francis pour le conseil ;)), je constate que la base de chaque pattern consiste en un ensemble de classes abstraites ou d'interfaces, alors que peu de programmeurs non-habitués les utilisent réellement à leur plein potentiel. C'est pourquoi j'essaie modestement dans cet article de démontrer la réelle puissance apportée par ces outils. :)

Beaucoup de programmeurs non habitués à la POO ne se servent que trés peu des interfaces, ou des classes virtuelles (quand elles sont disponibles dans le langage utilisé). Cet article se propose de cerner l'utilité de tels outils dans la programmation orientée objet.

Les codes exemples sont partagés entre du PHP5 et de l'Actionscript. En effet, alors que le premier offre la possibilité de faire des classes abstraites, l'autre est à mon sens plus lisible et permet un typage fort des types simples ! ;)

Définitions

Signature d'un fonction/méthode : Il s'agit de la "structure" d'une méthode. En gros, cela correspond aux arguments qu'elle prend en entrée, ainsi que leurs types, et le type de la valeur retournée.

Méthode abstraite (ou virtuelle) : Il s'agit d'une méthode qui ne contient pas d'implémentation. Une telle méthode oblige les classes qui vont hériter de la classe la contenant à implémenter une méthode dont la signature sera exactement la même.

Classe abstraite : Il s'agit d'une classe possédant au moins une méthode abstraite. Une classe abstraite n'est pas instanciable, du fait que l'ensemble de ses fonctionnalités n'est pas implémenté. Une classe abstraite permet de définir certaines implémentations par défaut, et à fournir la signature des méthodes non implémentées.
Pour l'Actionscript 2, Francis Bourre propose un pattern destiné à émuler les classes abstraites : http://www.tweenpix.net/archives/000316.html

Interface : L'interface est un ensemble de méthodes qui devront être implémentées dans les classes qui utilisent cette interface. Elle ressemble trés fortement à une classe abstraite qui ne contiendrait que des méthodes abstraites, et ne contient donc que des signatures de méthodes.

Différence entre classe abstraite et interface

Tout d'abord, la classe abstraite est utilisée par le biais de l'héritage. Il s'agit donc d'une relation de type "est un". Les classes qui hériteront d'une classe abstraite auront donc toutes un lien étroit concernant leur type. De plus, la plupart des langages n'autorisant plus l'héritage multiple, une classe ne peut souvent hériter que d'une seule classe, abstraite ou non. Il s'agit donc pour une classe abstraite de définir un "super type", dont d'autres types concrets pourront dériver.

Par exemple, on peut considérer que tous les véhicules à moteur se remplissent de la même manière : il suffit de mettre du carburant dans le réservoir. Tous les véhicules à moteur peuvent également être démarrés, mais chacun est démarré d'une manière qui lui est spécifique. Ainsi, il serait possible de créer une classe abstraite qui implémente le remplissage du réservoir, mais qui oblige chacune de ses sous-classes à implémenter la méthode de démarrage.

// Classe abstraite de véhicule a moteur
// Le replissage du reservoir possède une implémentation
// Par défaut
abstract class Vehicle {
   private var $_maxFuel;
   private var $_currentFuel;
   // Méthode concrète
   protected function fill($quantite) {
      // Code pour remplir le réservoir
   }
   // Methode abstraite
   // Les sous-classes devront l'implémenter
   abstract public function start();
}
// Voiture
// Heritant de VahiculeAMoteur, on est obligé d'implémenter démarrer
// Si on ne le fait pas, une erreur va survenir
class Car extends Vehicle {
   // Surcharge de la méthode abstraite
   public function start() {
      // Code pour démarrer la voiture
   }
}
L'interface elle, spécifie quelles vont être les possibilités d'une classe, sans en fournir la moindre implémentation. Elle est donc garante des méthodes d'accés à un objet dont la classe implémente cette interface. On peut dire que l'implémentation d'une interface est une relation de type "possède les fonctionnalités de". D'ailleurs, les interfaces ont souvent des noms synonymes de "capable de" (en plus du 'I' qui commence généralement leur nom) : IDragable, IViewable, etc... Par exemple, si l'on veut qu'un objet puisse être affiché, il est possible de créer une interface qui obligera la classe dont est issu l'objet d'implémenter une méthode display() ... Pour un objet qui doit pouvoir être déplacé par drag&drop, une interface pourra définir quelles sont les méthodes qui serviront à la manipuler pour cette tâche précise. // Interface définissant le comportement d'un objet
// que l'on peut déplacer à la souris
// Toute classe implémentant cette interface devra
// implémenter deux méthodes startDrag et stopDrag
interface IDragable {
   public function startDrag():Void;
   public function stopDrag():Void;
}
// Un curseur sur une jauge doit pouvoir
// être déplacé à la souris
class Cursor implements IDragable {
   public function startDrag() {
      // Code de début de déplacement
   }
   public function stopDrag() {
      // Code de fin de déplacement
   }
}
Utilisation Les interfaces et classe abstraites n'ont un véritable sens que si elles sont associées à un langage typé fortement, ou du moins qui permet d'imposer un type d'objet dans les signatures de méthodes (comme le "type hint" de PHP5). En effet, plus qu'un "carcan" imposé au développeur de classes, elles sont surtout utiles aux classes clientes, qui vont avoir besoin d'être assurée d'un certain comportement des objets qu'elles vont utiliser. En fait, pour pousser les choses plus loin, je dirait presque que pour une souplesse d'utilisation optimale, il ne faudrait utiliser que les interfaces et classes abstraites avec le typage fort. Il arrive souvent qu'un client ait besoin d'une implémentation précise, mais de toutes façons, le typage fort n'empêche pas une instance de sous-classe d'être utilisée, parfois avec une implémentation totalement différente. Ainsi, quand une méthode va afficher un objet, elle n'a pas forcément besoin de savoir de quel type est cet objet, mais uniquement de savoir si l'objet est "affichable" ! Donc, si le fait d'être affichable revient à implémenter une interface qui demande à ce qu'une méthode display() soit présente, on utilisera cette interface pour le typage fort. Et l'objet passé à la méthode peut être de n'importe quel type, on est alors sûr qu'il disposera d'une méthode display(), dont la signature est définie par l'interface. Prenons comme exemple une carte géographique. Cette carte est affichée en grand dans une fenêtre, et un ascenseur doit la déplacer. Pour cela, le déplacement de l'ascenseur recevra en paramètre l'objet qui représente la carte. Mais pour pouvoir la déplacer, il faut être certain que cette carte possède les méthodes qui vont nous permettre de la positionner. Il faut alors créer une interface imposant des méthodes d'accés. Ainsi, toute instance d'un classe qui implémente cette interface pourra être déplacée par l'ascenceur : // Interface de déplacement
interface IMovable {
   // Positionnement de l'objet
   public function moveToCoord(x:Number, y:Number);
   // Déplacement de l'objet
   public function moveByOffset(dx:Number, dy:Number);
}
// Classe d'une carte géographique
class Map implements IMovable {
   // Clip correspondant à l'affichage de la carte
   private var _targetMC:MovieClip;
   // Methode de positionnement
   public function moveToCoord(x:Number, y:Number) {
      this._targetMC._x = x;
      this._targetMC._y = y;
   }
   // Methode de déplacement
   public function moveByOffset(dx:Number, dy:Number) {
      this._targetMC._x += dx;
      this._targetMC._y += dy;
   }
}
// Scroller
class Scroller {
   function moveObject(obj:IMovable) {
      obj.moveByOffset(10,50);
   }
}

Ainsi, la classe Scroller devient appliquable à n'importe quel objet qui possède les méthodes nécessaires à son déplacement. Il peut tout aussi biuen s'agir d'un dessin ou d'un texte. Ces classes n'ont rien à voir entre elles, mais leur comportement est unifié par l'utilisation de l'interface.

Conclusion

Comme vous pouvez le constater, les interfaces et classes abstraites offrent un moyen d'assouplir votre code, le rendant ainsi plus évolutif. Le fait d'utiliser un comportement et non plus une classe dans ses typages forts permet de généraliser une instruction à un ensemble bien plus large d'objets.

Alors que les interfaces sont souvent utilisées uniquement comme structure de classes lorsqu'on les connait peu, les associer au typage fort en font un outil trés puissant !

28 avril 2004

Communautés Flash francophone sur divan

Le Flash Festival souhaite organiser une rencontre de flasheurs à l'occasion du festival. Voici donc le mail qui a été envoyé à tous ceux qui peuvent promouvoir cette trés bonne idée ! :D

Bonjour,

Nous aimerions susciter une grande rencontre des communautés à l'occasion de la 3e édition du Flash Festival en France, festival francophone du contenu animé et interactif sur Internet, organisé au centre Pompidou le samedi 08 mai 2004 !

Nous lançons un rendez-vous (après la soirée de remises des trophées), le samedi 08 mai à partir de 22h30 et jusqu'à 02h00, au Divan du monde qui sera l'occasion aux membres de nos communautés, d'échanger de visu et de se rencontrer ! L'entrée est bien sûr libre et ouverte à tous ! (pas d'open bar ;))

Nous invitons donc les différentes communautés qui le souhaitent (Flash France, Flash-Forum, VisionFlash, Flashxpress et les blogs Lalex, TweenPix et GotoAndPlay) à communiquer sur ce rendez-vous dans leurs forums ou blogs pour que celui-ci soit une réussite et un grand moment d'échange et de rencontre !

N'hésitez pas à revenir vers nous pour toutes suggestions :)

Le Divan du monde : http://www.divandumonde.com/
Flash Festival en France : http://www.flashfestival.net/

Divan du monde (Angle des rues La Cigale / chez Michou. Métro Pigalle, ligne 2 ou 12).
Plan: par ici

Bon, je suis super déçu de pas pouvoir y aller ... mais je conseille vivement à ceux qui en ont la possibilité de s'y rendre, ca devrait être super sympa ! 8)

26 avril 2004

Erratum article sur le pathfinding A star

Patrick Lester, auteur de l'article sur le pathfinding A* que j'ai traduit, me signale qu'en lisant les commentaires fait sur ce blog à la suite de l'article, il s'est rendu compte d'une erreur, signalée par Yogaman. En effet, dans le cas où les diagonales sont utilisées, l'heuristique Manhatan peut retourner une estimation supérieure à la distance restant à parcourir. Or, pour une fiabilité complète, il faut que l'estimation de la distance à parcourir soit inférieure ou égale à la distance restant réellement à parcourir ...

Il est maintenant signalé que la méthode Manhatan est inadmissible pour l'exemple donné, mais pour des raisons de simplification, elle reste celle qui sera utilisée dans l'exemple de l'article, comme l'a signalé l'auteur dans son article original.

Mon implémentation de l'algorithme se retrouve donc avec la même erreur, faisant que le chemin retourné peut ne pas être le plus court (toujours dans le cas des diagonales autorisées) ... Il s'agit donc de modifier la méthode heuristic pour prendre en compte les diagonales ou non. C'est là que je suis bien content d'avoir fait de l'heuristique une méthode statique indépendante ! 8) A ce propos, je rappelle que le cout par défaut utilisé est de 10. Dans le cas où les couts de chaque case peuvent varier (couts des terrains, contenu dans chaque case de la carte), il faut donc mettre le coût minimum dans la propriété statique DEFAULT_COST ... :)

Voici donc la version modifiée du pathfinder pour une plus grande fiabilité. J'ai également joint la classe Grid dont je me sert maintenant (tableau à deux dimensions), et modifié un peu le package de la classe (maintenant com.lalex.game) ...

::Télécharger pathfinder.zip::

1 mars 2004

ActionScript : Les secrets des prototypes

Les prototypes sont la base de la POO avec Flash, même en AS2. C'est pourquoi j'ai considéré qu'il pouvait être sympa de faire une explication détaillée de leur fonctionnement, dans un didacticiel rédigé pour MediaBox. En fait, l'idée de ce tutoriel m'est venue il y a un petit moment deja, quand dans un éclair, j'ai fini par comprendre pourquoi la méthode d'héritage préconisée par Macromedia faisait effectivement de l'héritage .... :D

Auteur : LAlex
Mail : lalex@flash-forum.net
URL : http://www.flash-forum.net
Date de création : 26/02/2004
Version : Flash 5 / MX / MX 2004

Pré requis
:arrow: Connaître les principes de la POO
:arrow: Pour appliquer dans Flash MX 2004 : De ActionScript 1 à ActionScript 2

Introduction

Le programmation orientée objet sous Flash est dite "prototype based", soit en français "basée sur les prototypes". Ce type de programmation est opposée à la programmation dite "class based", utilisée par la plupart des langages objets célèbres, tels que le C/C++/C# ou Java. Bien qu'essayant de simuler la même syntaxe que ces derniers langages, l'ActionScript 2 reste un langage de prototypes, transformé en ActionScript 1 lors de la compilation. C'est pourquoi il est également important de connaître les rouages de cette mécanique même si l'on utilise la dernière mouture de Flash.

Bien que limitant quelques possibilités très avancées de la POO, les prototypes sont également un outil fantastique, permettant une programmation plus souple, et une compréhension plus facile du fonctionnement interne de votre code. Nous allons ici faire le tour des spécificités de la programmation à base de prototypes, et plus particulièrement celle de Flash.

1. D'ou vient le prototype ?

Nous partirons du constat que toute fonction à un prototype, qui est créé en même temps que la fonction. Le prototype est un objet, contenant des propriétés et des méthodes. En fait, il n'est pas nécessaire d'en savoir plus sur ce point. D'ailleurs, l'aide de Flash nous donne peu d'informations sur ce qu'est le prototype, et se contente de dire Dans une fonction constructeur ActionScript 1, la propriété prototype fait référence à un objet qui est le prototype de la classe construite.Nous parlons ici de fonction constructeur, c'est à dire qui va servir à instancier un nouvel objet. Or, toute fonction peut être utilisée comme constructeur. ActionScript 2 fonctionne de la même manière, et les équivalences avec AS1 sont décrites dans le tutorial "D'ActionScript 1 à ActionScript 2".

Lorsque l'on crée une fonction, on peut considérer que Flash crée automatiquement un prototype associé, de type Object :

// Création d'une fonction
myFunction = function() {
   trace("myFunction a été appellée");
}

// Instruction implicite
// myFunction.prototype = {}
2. L'opérateur new() Elément de base de la POO, l'opérateur new() sert à créer un nouvel objet à partir d'une fonction constructeur. Son fonctionnement consiste à attribuer le prototype de la fonction constructeur à un objet nouvellement crée. Examinons les différentes étapes de la création de cet objet. - L'opérateur new() crée un nouvel objet - Il initialise la propriété système "__proto__" de ce nouvel objet avec le prototype de la fonction constructeur - Il initialise la propriété système "__constructor__" de ce nouvel objet avec la fonction constructeur - Il exécute la fonction sur l'objet nouvellement crée Nous pouvons reproduire le fonctionnement de new() de la manière suivante : // Déclaration du constructeur
function MaClasse(arg) {
   trace("Instance de MaClasse créée avec le paramètre : '" + arg + "'");
}

// Instruction new "classique"
var monInstance = new MaClasse("Argument");
// Même instruction décomposée
// On crée un nouvel objet
var monInstance = new Object();
// On lui attribue le prototype de la fonction
monInstance.__proto__ = MaClasse.prototype;
// On lui attribue le constructeur
monInstance.__constructor__ = MaClasse;
// On applique la fonction constructeur à l'objet
MaClasse.call(monInstance, "Argument");
3. __proto__ et la chaîne de prototypes
Nous avons vu précédemment que la construction d'un objet consiste à mettre un prototype dans sa propriété __proto__. Il s'agit maintenant de comprendre pourquoi l'on peut accéder aux méthodes et propriétés d'un objet, alors qu'elles sont créées dans le prototype. Nous allons prendre l'exemple d'un appel de méthode. En fait, lorsque l'on accède à la propriété d'un objet, Flash va chercher tout d'abord si cette méthode existe dans l'objet lui-même. Si c'est le cas, c'est cette méthode qui va être utilisée. Dans le cas contraire, Flash va rechercher dans la propriété __proto__ de l'objet si la méthode est présente, et l'utiliser s'il la trouve. S'il ne la trouve toujours pas, étant donné que __proto__ est un objet, il contient lui-même une propriété __proto__, dans laquelle Flash va continuer sa recherche, et ainsi de suite, jusqu'à ne plus avoir de __proto__ dans lequel rechercher. Voici une illustration du fonctionnement : // Classe "maison"
function MaClasse (arg) {
   trace("Instance créée avec l'argument '" + arg + "'");
}

// Ajout d'une méthode dans le prototype
MaClasse.prototype.innerDoIt = function(arg) {
        trace("innerDoIt exécuté");
}
// Création d'une instance
monInstance = new MaClasse("Argument");
// Création d'une fonction dans l'instance
monInstance.doIt = function() {
        trace("doIt exécuté");
}
// Appel des différentes méthodes
monInstance.doIt();
monInstance.innerDoIt();
trace(monInstance.toString());
Instance créée avec l'argument 'Argument' doIt exécuté innerDoIt exécuté [object Object] Ce code a en fait exécuté les méthodes suivantes : - Constructeur - monInstance.doIt(); - monInstance.__proto__.innerDoIt(); - monInstance.__proto__.__proto__.toString() (méthode toString() de Object) Cette succession de recherche s'appelle "parcourir la chaîne de prototypes". La fin du parcours se fait lorsque la méthode est trouvée, ou lorsque la chaîne se termine. Nous reviendrons plus tard sur la raison pour laquelle la chaîne se termine.

4. L'héritage

Il existe deux méthodes pour implémenter l'héritage en ActionScript 1. L'instruction 'extends' d'AS2 est un mixage des deux, assez bizarre il faut bien le dire ... Pour rappel, l'héritage permet de profiter des fonctionnalités d'une classe mère dans une classe fille qui en hérite. Pour plus d'informations, rendez-vous sur le tutorial concernant les principes de la POO.
La méthode officielle de Macromedia
Elle consiste à faire du prototype de la classe fille une instance de la classe mère. Les instances de cette classe fille possèderont donc le prototype de la classe mère dans leur chaîne de prototypes. // Classe mère<br />
Parent = function() {<br />
      trace("Nouvelle instance de Parent");<br />
}<br />
// Classe fille<br />
Child = function() {<br />
       trace("Nouvelle instance de Child");<br />
}<br />
// La classe fille hérite de la classe mère<br />
Child.prototype = new Parent()<br />
// Instanciation de la classe fille<br />
monInstance = new Child();
Pourquoi cette instruction permet-elle de faire de l'héritage ? Penchons nous en détail dessus, en se servant de ce que nous avons vu ci-dessus concernant l'instruction new() ... Si l'on utilise la notation "équivalente" de new, nous obtiendrions : // Classe mere<br />
Parent = function() {<br />
   trace("Nouvelle instance de Parent");<br />
}<br />
// Classe fille<br />
Child = function() {<br />
   trace("Nouvelle instance de Child");<br />
}<br />
// "Traduction" de Child.prototype = new Parent()<br />
Child.prototype = new Object();<br />
Child.prototype.__proto__ = Parent.prototype;<br />
Child.prototype.__constructor__ = Parent;<br />
Parent.call(Child.prototype);<br />
// "Traduction" de monInstance = new Child()<br />
monInstance = new Object();<br />
monInstance.__proto__ = Child.prototype;<br />
monInstance.__constructor__ = Child;<br />
Child.call(monInstance);
Nous avons donc dans monInstance.__proto__ = Child.prototype, et plus haut, nous avions Child.prototype.__proto__ = Parent.prototype ... Donc, on peut en déduire que monInstance.__proto__.__proto__ = Parent.prototype ! 8) Lorsque l'on va accéder à une méthode de monInstance, conformément au parcours de la chaîne de prototypes, Flash va d'abord chercher dans monInstance, puis dans monInstance.__proto__ (prototype de Child), puis dans monInstance.__proto__.__proto__ (prototype de Parent). Nous avons donc bien de l'héritage !

La méthode plus "propre".
Dans la "traduction" ci-dessus, on peut se rendre compte que toutes les lignes ne sont pas utiles. En effet, si l'on regarde la sortie générée par ce code, on peut voir Nouvelle instance de Parent Nouvelle instance de ChildEn effet, le constructeur de Parent a été appelé, alors qu'il n'est absolument pas nécessaire. Afin d'éviter cela, une méthode d'héritage préférable consiste à utiliser les lignes de traduction, mais uniquement celles qui sont nécessaires. L'instanciation de monInstance peut être refaite par l'opérateur new(), étant donné que le principe reste le même que ci dessus : // Classe mere<br />
Parent = function() {<br />
   trace("Nouvelle instance de Parent");<br />
}<br />
// Classe fille<br />
Child = function() {<br />
   trace("Nouvelle instance de Child");<br />
}<br />
// Héritage<br />
// ** Child.prototype = new Object();<br />
// Cette ligne n'est pas nécessaire, étant donné que<br />
// le prototype de Child est crée automatiquement à la<br />
// création de la fonction<br />
Child.prototype.__proto__ = Parent.prototype;<br />
Child.prototype.__constructor__ = Parent;<br />
// ** Parent.call(Child.prototype);<br />
// Cette ligne n'est pas nécessaire : il est inutile<br />
// d'exécuter le constructeur de la classe mère<br />
// "Traduction" de monInstance = new Child()<br />
monInstance = new Child()
Ainsi, les fonctionnalités de l'héritage sont préservées, sans qu'aucune instruction inutile ne soit executée. :idea: Les spécifications du format SWF v7 donnent l'implémentation de l'instruction extends en ActionScript 2: Subclass.prototype = new Object();<br />
Subclass.prototype.__proto__ = Superclass.prototype;<br />
Subclass.prototype.__constructor__ = Superclass;
Cette implémentation a fait débat, concernant le fait qu'en executant la première ligne, à priori inutile, elle détruit le prototype actuel, détruisant ainsi le prototype courant, et la propriété "constructor" de celui-ci ... Vous pouvez avoir plus d'informations sur la discussion en français qui a eu lieu sur le devblog de LAlex.

5. Pourquoi parle-t-on de "prototypes de MovieClip" ? Lorsque l'on parle de prototypes de MovieClip comme les petits utilitaires permettant de faire un mouvement élastique ou autre, il s'agit d'un abus de langage, issu des débuts de la programmation Flash durant lesquels le vocabulaire propre à la POO n'était pas des plus répandus chez les programmeur utilisant cette technologie. En réalité, pour être exact, faire un "prototype de MovieClip" signifie "rajouter une méthode au prototype de MovieClip". Etant donné que tous les clips de Flash on un __proto__ qui pointe vers MovieClip.prototype, ajouter une méthode à ce prototype revient à ajouter une méthode a chaque clip !!!

6. La cas particulier de Object
Nous parlions plus haut de parcourir la chaîne de prototypes jusqu'à ce qu'il n'y ai plus de prototype à parcourir. La question est de savoir à quel moment s'arrête cette chaîne. Le cas particulier est en fait la classe Object. Elle a pour particularité le fait que son prototype n'a pas de constructeur. Elle possède bien un prototype, comme toute fonction constructeur, c'est pourquoi il est possible d'utiliser new Object(). Par contre ce prototype n'est issu d'aucune classe, ce qui fait que le __proto__ de Object.prototype n'existe pas. C'est comme si l'instruction implicite (vue au début de ce tutoriel) à la création de la fonction "Object" n'avait pas été exécutée. En effet, cela se vérifie facilement : // Classe interne<br />
trace("== MovieClip ==");<br />
trace(MovieClip.prototype);<br />
trace(MovieClip.prototype.__proto__);<br />
// Classe "maison"<br />
MaClasse = function() {}<br />
trace("== MaClasse ==");<br />
trace(MaClasse.prototype);<br />
trace(MaClasse.prototype.__proto__);<br />
// Classe Object<br />
trace("== Object ==");<br />
trace(Object.prototye);<br />
trace(Object.prototype.__proto__);

== MovieClip==
[object Object]
[object Object]

== MaClasse ==
[object Object]
[object Object]

== Object ==
[object Object]
undefined

C'est la raison pour laquelle la remontée de la chaîne de prototypes s'arrête à un moment donné, lorsqu'elle rencontre un __proto__ qui n'existe pas. Cette constatation nous amène aussi au fait que toutes les classes héritent forcément de Object, étant donné que leurs prototypes sont soit de type Object, soit héritent eux-mêmes de Object. C'est d'ailleurs pour ca que toutes les classes, personnelles ou intégrées à Flash, possèdent une méthode "toString"

Conclusion

Une fois les prototypes compris, se balader dans les chaînes de prototypes devient beaucoup plus clair, et l'on arrive ainsi à une compréhension accrue du fonctionnement de son code, ce qui simplifie évidemment son élaboration ainsi que son debugage.
Les prototypes peuvent également poser des problématiques inédites, véritable gymnastique de l'esprit. Les manipuler peut amener à des fonctionnalités très pointues, directement au coeur du code, traitant les classes elles-mêmes comme l'on traite des objets "normaux".

22 janvier 2004

Quelle place pour Flash dans le développement d'applications online ?

Ma première contribution au webzine EMMA de MediaBox est un article sur la place de Flash dans les applications online. Je parle ici de Flash en tant qu'outil de développement pour des applicatifs avancés, et de la difficulté qu'à ce format à s'imposer dans le monde de l'entreprise, que ce soit pour des applications Intranet ou des gros sites dynamiques tels que Amazon.

J'y fais l'analyse non-exhaustive des raisons qui pourraient pousser à l'adopter, et des raisons qui rendent les décisionnaires souvent frileux à utiliser une telle technologie. Doit-on voir du Flash partout ? Pourquoi pas ! 8) Pourquoi n'est-ce pas le cas ? Je vous livre mon explication ... :D

15 septembre 2003

Traduction : article sur le pathfinding A*

Voici l'article qui m'a fait comprendre le pathfinding ! :D

Je l'ai traduit en français afin que le plus grand nombre puisse en profiter. L'auteur se nomme Patrick Lester et l'article original se situe à l'adresse suivante : http://www.policyalmanac.org/games/aStarTutorial.htm

Pathfinding A* pour les débutants

Alors qu'il est relativement facile à utiliser une fois que l'on a pris le coup, l'algorithme A* (prononcer "A star") peut être difficle à aborder pour des débutants. Il existe beaucoup d'articles sur le web qui expliquent A*, mais la plupart sont destinés à ceux qui en comprennent dejà les bases. Celui-ci est destiné aux vrais débutants du pathfinding.

Cet article n'est pas spécifique à un langage donc vous devrier facilement pouvoir l'adapter a n'importe quel langage de programmation.

Introduction : la zone de recherche.

Considérons que quelqu'un veut aller d'un point A à un opint B. Considérons également qu'un mur est situé entre ces deux points. Cette situation est illustrée par l'image ci-contre, où le point A (départ) est affiché en vert, le point B (arrivée) est en rouge, et où les cases en bleu représentent le mur.

La première chose que vous remarquerez est que la zone de recherche est divisée en cases. Simplifier la zone de recherche, comme nous l'avons fait ici, est la première étape en pathfinding. Cette méthode particulière réduit notre zone de recherche à un simple tableau a deux dimensions. Chaque élément du tableau représente une case de notre grille, est son statut est notifié comme étant "traversable" ou "non traversable". Le chemin va être la succession de cases à parcourir pour passer de la case A à la case B. Une fois le chemin trouvé, notre personnage va passer du centre d'une case au centre de la prochaine case, jusqu'à ce qu'il atteigne sa cible.

Ces points au centre de chaque case sont appelés "noeuds" (nodes). Quand vous lisez un texte traitant du pathfinding, vous retrouverez souvent ce terme de noeuds. Pourquoi ne pas appeler ca simplement une case ? Tout simplement parce qu'il est possible de diviser une zone de recherche en autre chose qu'une case "carrée". Cela peut-être une forme rectangulaire, ou hexagonale, ou en fait n'importe quelle forme. Ces noeuds peuvent égelement être placés n'importe ou sur les formes - au centre ou sur les contours, ou n'importe où ailleurs. Nous utilisons ici ce système tout simplement parce que c'est le plus simple ...

Commencer la recherche

Une fois la zone de recherche simplifiée à un nombre de noeuds facilement gérable, comme nous l'avons fait avec la grille ci-dessus, la prochaine étape consiste à effectuer la recherche pour trouver le chemin le plus court. En pathfinding A*, on commence cette recherche par le point A, puis en vérifiant les cases adjacentes, et en général en cherchant encore à l'extérieur jusqu'à ce que la cible soit trouvée.

Nous commencons notre recherche de la manière suivante :

Commencons au point de départ et ajoutons le a une "liste ouverte" (open list) de cases à étudier. La liste ouverte est une sorte de "shopping list". Pour l'instant, nous n'avons qu'un seul point à l'intérieur (le point A), mais il y en aura bientôt plus. Elle contient une liste de cases qui pourraient éventuellement faire partie de notre chemin, mais pas forcément. En fait, c'est une liste de pionts que nous devrons vérifier. Regardez maintenant toutes les cases ateignables (ou "traversables") adjacentes au point de départ, en ignorant les cases avec les murs, ou de l'eau, ou toute case qu'on ne peut pas traverser. Ajoutez les a la liste ouverte également. Pour chacune de ces cases, enregistrez la case A comme son étant son "parent". Cette notion de "parent" est trés importante ensuite pour pouvoir retracer le chemin. Une explication vous est donnée plus loin.
Supprimez maintenant le point A de la "liste ouverte", et ajoutez le à une "liste fermée" (closed list), qui va contenir les points que nous n'aurons plus besoin de vérifier.

A cette étape, vous devriez avoir quelquechose comme l'image ci-contre. Sur ce schéma, la carré vert foncé au centre est notre case de départ. Elle est entourée de bleu clair pour signifier que la case a bien été ajoutée à notre "liste fermée". Toutes les cases adjacentes sont maintenant dans la "liste ouverte" de case à vérifier, et sont entourées de vert clair. Chacune d'elle possède une "pointeur" vers son parent, qui n'est autre que la case de départ.

Ensuite, choisissons une case de la "liste ouvert", et répétons plus ou moins le raisonnement précédent. Mais quelle case choisir ? Celle avec le coût F le plus bas.

Cout d'un chemin.

La clé servant à déterminer quelle case utiliser parmi celles contenues dans la "liste ouverte" est l'équation suivante :
F = G + H
avec

  • G est le coût de mouvement pour aller de la case A à une case donnée sur la grille, en suivant le chemin généré jusqu'ici.
  • H est le coût de mouvement pour aller d'une case donnée sur la grille jusqu'au point de destination, le point B. Il est souvent appelé "heuristique", ce qui peut-être troublant. La raison pour laquelle on le nomme ainsi est que ce coût est estimé. En effet, nous ne connaissons pas vraiment la distance qu'il nous reste à parcourir, car toute sortes d'obstacles peuvent se trouver sur le parcours (mur, eau, etc..). Un moyen de calculer ce coût est donné dans ce tutoriel, mais il en existe d'autres que vous pourrez trouver dans d'autres articles sur le net.

Notre chemin est généré récursivement en parcourant notre "liste ouverte" et en y choisissant le case ayant le coût F le plus faible. Ce raisonnement sera abordé un peu plus loin dans cet article, mais regardons d'abord commentutiliser l'équation que nous venons de voir.

Comme décris précédemment, G est le coût de mouvement du point de départ jusqu'à la case donnée en parcourant le chamin généré jusque là. Dans cet exemple, nous allons assigner un coût de 10 pour chaque déplacement horizontal ou vertical, et un coût de 14 pour un mouvement en diagonale. Nous utilisons ces données car la distance nécessaire pour se déplacer est la racine carrée de 2 (ne prenez par peur, restez la ! ;)), ou approximativement 1.414 fois le coût d'un déplacement vertical ou horizontal. Nous utiliserons 10 et 14 pour des raisons évidentes de simplification. Le rapport est à peu prés correct, et nous évitons ainsi les calculs de racines carrées et le nombres à virgule. Mais cela n'est pas seulement pour que nous n'aimons pas les maths : utiliser des nombres entiers est aussi bien plus rapide pour l'ordinateur également. Comme vous allez bientôt le constater, le pathfinding peut être trés lent si vous n'utilisez pas ce genre d'astuces.

Etant donné que nous calculont le coût G le long d'un chemin donné, le moyen de trouver ce coût est de prendre le coût G de son parent, et de lui ajouter 10 ou 14 selon qu'il soit situé en diagonale ou pas par rapport à son parent. L'interêt de cette méthode deviendra plus clair lorsque nous serons plus loin qu'à une case du point de départ.

Le coût H peut être estimé d'un grand nombre de facons. La méthode que nous utiliserons ici est nommée la méthode "Manhattan", avec laquelle vous calculez le nombre de cases verticales et horizontales pour parvenir au point d'arrivée (en ignorant les mouvements en diagonale). Nous multiplions alors ce total par 10. Cette méthode s'appelle "Manhattan" parce qu'elle revient à calculer le nombre de patés de maisons d'un endroit à un autre, sans couper à travers un bloc en diagonale.

En lisant ceci, il est courant de penser que l'heuristique est simplement une estimation de la distance restant à parcourir à vol d'oiseau. Or, ce n'est pas le cas. Nous essayons en fait de calculer la distance restant à parcourir via le chemin à trouver (qui est généralement plus long). Plus cette estimation est proche, plus l'algorithme trouvera le chemin facilement (et rapidement). Si le chemin estimé est sur-évalué, il n'est pas certain de trouver le chemin optimal. Dans ce cas, l'heuristique est dite "inadmissible".

Techniquement, dans cet exemple, la méthode "Manhatan" est inadmissible, car elle surévalue quelque peu la distance restante. Mais nous l'utiliserons quand-même, car elle est la plus facile à comprendre dans ce cas là, et qu'il s'agit d'une surestimation assez légère. Dans les rares cas où le chemin trouvé n'est pas le plus court, il n'en sera pas trés éloigné. Si vous voulez en savoir plus, rendez vous ici.

Le coût F est calculé en ajoutant G et H. Vous pouvez constater le résultat sur le schéma ci-contre. Les coûts F, G et H sont notés dans chaque case. Comme indiqué, F est situé en haut à gauche, G en bas à gauche et H en bas à droite.

Jetons maintenant un coup d'oeil à ces cases. Dans celle qui contient les lettres, nous avons G=10. C'est parce qu'elle est située à une case du point de départ, dans une direction horizontale. Les cases en dessous et au dessus de la case de départ, ainsi que celle située à sa gauche ont toutes le même coût de 10. Les cases en diagonales ont un coût de 14.

Les coûts H sont calculés en estimant la distance "Manhattan" jusqu'à la case d'arrivée, en se déplacant horizontallement et verticalement, et en ignorant le mur qui est sur le chemin. Avec cette méthode, la case située juste à droite du départ est à trois déplacement horizontaux de l'arrivée, soit un coût H de 30. La case située juste en dessous est à quatre cases de l'arrivée (souvenez-vous, on se déplace uniquement horizontalement et verticalement) pour un coût H de 40. Vous constaterez facilement le calcul des coûts H des autres cases.

Le coût F de chaque case est calculé par une simple addition de G et H.

Continuer la recherche

Pour continuer la recherche, nous choisissone tout simplement la case ayant le coût F le plus faible parmi les case de la "liste ouverte". Puis, avec cette case nous faisons le processus suivant :

  1. Nous la supprimons de la "liste ouverte" et la rajoutons à la "liste fermée"
  2. Nous vérifions toutes ses cases adjacentes, en ignorant celles qui font partie de la "list fermée", ainsi que celle qu'on ne peut pas traverser, que nous ajoutons à la "liste ouverte" si elles n'y sont pas déjà. Assignez la case en cours comme étant le "parent" des cases nouvellement ajoutées.
  3. Si une des cases adjacentes est déjà dans la "liste ouverte", vérifiez si le chemin pour y arriver n'est pas meilleur. En d'autres termes, vérifiez si le coût G de cette case est inférieure si nous utilisons la case en cours pour y parvenir. Si ce n'est pas le cas, ne changez rien.
    D'un autre côté, si le coût G du nouveau chemin est inférieur, faites que la case en cours soit le nouveau parent de cette case adjacente (dans le schéma précédent, chagez la direction du pointeur pour pointer vers la case en cours). Pour finir, re-calculez les coût F et G de cette case. Cela semble assez confus, mais vous le verrez illustré graphiquement ci-dessous.

Bon, voyons maintenant comment cela fonctionne. Sur nos 9 cases initiales, nous en avons 8 qui font partie de la "liste ouverte" aprés que la case de départ ait été envoyée vers la "liste fermée". De ces 8 cases, celle ayant le coût F le plus faible est celle qui se situe immédiatement à droite de la case de départ, avec un coût F de 40. Donc, nous choisissons cette case comme étant la prochaine. Elle est entourée en bleu dans l'illustration ci-contre.

Tout d'abord, nous supprimons cette case de la "liste ouverte" pour l'envoyer dans la "liste fermée" (c'est pourquoi elle est maintenant entourée en bleu). Puis, nous vérifions ses cases adjacentes. Celle qui est immédiatement à droite est un mur, donc nous l'ignorons. Celle immédiatement à gauche est la case de départ. Comme elle fait partie de la "liste fermée", on l'ignore également.

Les quatre autres cases sont deja dans la "liste ouverte", donc nous devons vérifier si les chemins qui y vont sont meilleurs en passant par notre case en cour, en utilisant le coût G comme point de comparaison. Regardons la case située juste en dessous de notre case actuelle. Son coût G est de 14. Si nous y allions en passant par notre case en cours, le coût serait de 20 (10 de coût G de la case, plus 10 de déplacement vertical depuis la case en cours). Un coût G de 20 est supérieur à 14, donc le chemin en cours n'est pas meilleur. Il suffit de regarder le schéma pour que cela saute aux yeux : il est plus direct d'eller sur cette case depuis le départ en se déplacant en diagonale, plutôt qu'en se déplacant d'abord horizontalement d'une case puis verticalement d'une case.

Lorsque nous répétons ce processus pour les quatre cases adjacentes deja dans la "liste ouverte", nous nous rendons compte qu'aucun chemin n'est amélioré en passant par la case en cours, donc nous ne changeons rien. Maintenant que nous avons traité toutes les cases adjacentes, nous en avons fini avec la case en cours, et nous sommes prêts à passer à la suivante.

Donc nous parcourons a nouveau notre "liste ouverte", qui est maintenant composée de 7 cases, et nous prenons celle qui a le coût F le plus faible. Il est interessant de constater que dans ce cas, il y a deux cases avec un coût de 54. Dans ce cas, laquelle choisir ? En fait, cela a peu d'importance. Pour des raisons d'optimisation, il peut être plus rapide de choisir le dernier que vous avez ajouté à la "liste ouverte". Cela oriente la recherche en faveur des cases qui sont trouvées le plus tard au cours de la recherche, quand vous êtes arrivés plus prés de votre destination. Mais cela n'a réellement pas d'importance (différentes approches de cette notion peuvent faire que deux algorithmes A* peuvent trouver des chemins différents de longueurs égales).

Choisissons la case située juste en dessous et à droite de la case de départ, comme illustré dans le schéma ci-contre.

Cette fois-ci, quand nous vérifions les cases adjacentes, nous voyons que celle située immédiatement à droite est un mur, donc nous l'ignorons. Nous faisons la même constatation pour celle située juste au dessus. Nous ignorerons également celle qui est située en dessous du mur. Pourquoi ? Parce que vous ne pouvez pas vous y rendre directement depuis la case en cours sans passer au dessus du coin du mur. Vous avez en fait besoin d'aller vers le bas d'abord, pour ensuite vous rendre sur cette case (Note : cette règle est optionnelle. Elle dépend en fait de l'endroit ou sont situés les "noeuds").

Ceci nous laisse cing autres cases. Les deux cases situées en dessous de la case en cours ne sont pas encore dans la "liste ouverte", donc nous les ajoutons avec la case en cours comme parent. En ce qui concerne les trois autres cases, deux sont deja dans la "liste fermée" (la case de départ et celle située à sa droite, toutes les deux entourées en bleu sur le schéma), donc nous les ignorons. Et la dernière case, immédiatement à gauche de la case en cours, est vérifiée pour voir si le coût G est inférieur en passant par la case en cours. Ce n'est pas le cas, donc nous pouvons vérifier la case suivante dans notre "liste ouverte".

Nous allons répeter ce processus jusqu'à ce que la case d'arrivée soit ajoutée à la "liste fermée", ce qui nous donne quelque chose comme le schéma ci-contre.

Vous contaterez que le parent de la case située deux cases en dessous du départ a changé par rapport au précédent schéma. Avant, elle avait un coût G de 28, et pointait vers la case situé en haut à droite. Maintenant elle a un score de 20 et pointe vers la case juste au dessus d'elle. Ceci est arrivé quelque part au long de notre recherche, alors que le coût G a été vérifié et modifié pour être plus faible en utilisant un autre chemin - donc le parent a été changé, et les coûts F et G recalculés. Bien que ce changement paraisse anodin ici, il existe beaucoup de situations ou cette vérification constante va faire la différence pour déterminer le meilleur chemin jusqu'à votre destination.

Maintenant, comment déterminer le chemin lui-même ? Tout simplement en partant de la case d'arrivée, et en remontant le chemin en sens inverse d'une case à sa case parent, en suivant les flèches. Cela va vous ramener à votre chemin de départ, et voila votre chemin ! Cela devrait ressembler à l'illustration ci-contre. Se déplacer du point A ou point B consiste maintenant à vous déplacer du centre d'une case au centre de la prochaine case de votre chemin, jusqu'à ce que vous atteignez votre destination. Simple non ?!?

Récapitulatif de la méthode A*

Maintenant que nous avons effectué la totalité de nos explications, faisons un petit récapitulatif étape par étape :

  1. Ajouter le point de départ à la "liste ouverte"
  2. Répéter les instructions suivantes :
    1. Cherche la case ayant le coût F le plus faible dans la "liste ouverte". Elle devient la case en cours.
    2. Passer la case en cours de la "liste ouverte" à la "liste fermée"
    3. Pour les 8 case adjacentes à la case en cours
      • Si on ne peut pas la traverser, on l'ignore.
      • Si elle n'est pas dans la "liste ouverte", on l'y ajoute. La case en cours devient le parent de cette case. On calcule les coûts F, G et H de cette case.
      • Si elle est déjà dans la "liste ouverte", on teste si le chemin passant par la case en cours est meilleur en comparant les coûts G. Un coût G inférieur signifie un meilleur chemin. Si c'est le cas, on change le parent de la case pour devenir la case en cours, en on recalcule les coûts F et G. Si vous conservez une "liste ouverte" triée par coût F, la liste doit être retriée à ce moment la.
    4. On s'arrête quand
      • La case de destination est ajoutée à la "liste fermée"
      • Vous ne trouvez pas la case de destination et la "liste ouverte" est vide.
  3. Enregistrez le chemin. En partant de la case de destination, remontez d'un case à son parent jusqu'à atteindre la case de départ. Vous avez votre chemin ! :D

Petit apparté

Excusez moi pour ce hors-sujet, mais il est important de noter qu'à de nombreuses discussions concernant le pathfinding A* sur le web et dans des forums, vous trouverez parfois certains codes qui sont dit A*, mais ne le sont pas. Pour utiliser la méthode A*, il faut absolument inclure les notions abordées précedemment, et plus précisemment la "liste ouverte" et la "liste fermée", ainsi que le calcul des coûts F, G et H. Il existe beaucoup d'algorithmes de pathfinding, mais ces autres méthodes ne sont pas A*, qui est générallement considéré comme étant le meileur de tous. Bryan Stout aborde beaucoup d'entre eux dans cet article, incluant leurs avantages et inconvénients. Parfois, une alternative est mailleure dans certaine circonstances, mais réflechissez à ce dans quoi vous mettez les pieds!

12 septembre 2003

Flash MX 2004 : d'ActionsScript 1 à ActionScript 2

Voici un tutoriel toujours écrit pour Flash Forum qui décrit comment passer de la POO avec ActionScript 1 (Flash MX) à la POO avec ActionScript 2 (Flash MX 2004)

Je commence par décrire comment traduire les instructions utilisées en POO avec Flash MX, presque mot à mot, vers une POO faite avec AS2. Viennent ensuite les particularité propres au langage AS2, suivie d'une brève description des habitudes qu'il faudrait prendre pour coder avec Flash MX 2004 ...

Passer de l'ActionScript 1 à l'ActionScript 2

Auteur : LAlex
Mail : lalex@flash-forum.net
URL : http://www.flash-forum.net
Date de création : 20/09/2003
Version : Flash MX 2004

Présentation

:idea: Tous les programmeurs POO en Flash MX ne sont pas forcément habitués à des languages objets tels Java ou C++. Dans sa nouvelle version MX 2004, Macromedia introduit ActionScript 2, qui a pour but, entre autre, de ralier à sa cause les programmeurs utilisant des langages "standards", en utilisant une syntaxe trés proches de celle dont ils ont l'habitude. Pour les autres, voici comment passer en douceur à l'ActionScript 2.0.
img:coupe
1. Différences des concepts

:arrow: Si vous vous interessé à ce qui se passe dans le monde de l'ActionScript (nous utiliserons l'abbréviation "AS" dans le tutoriel) vous avez peut-être deja lu ou entendu que l'AS de Flash MX permet de faire de la POO basé sur les prototypes (prototype based). A l'inverse, AS2 propose de faire de la POO basée sur les classes (class based). Voyons quels sont ces deux principes:


  • POO avec les prototypes : Pour créer une classe, on crée en fait une fonction qui sera la fonction contructeur de la classe. Ce n'est pour l'instant rien d'autre qu'une fonction. C'est le fait de l'utiliser avec l'opérateur 'new' qui en fait une classe.
    Pour rajouter une méthode à un objet, on va alors créer des fonctions dans son prototype. Lorsque l'on va appeler une méthode de l'objet, cet appel va vérifier si la méthode existe dans l'objet. Si ce n'est pas le cas, elle va regarder si elle existe dans le prototype du constructeur (puis dans le prototype du prototype, etc...) : c'est ce que l'on appelle la "chaîne de prototype". C'est comme cela qu'est gérée le fait que plusieurs objets puissent avoir la même méthode. En effet, si tous les objets ont utilisés le même constructeur, le prototype du constructeur sera le même pour tous ces objets. Pour faire de l'héritage, on rajoute tout simplement un prototype à l'endroit voulu dans la chaine de prototype. Il existe d'autres utilisations avancées des prototypes, mais nous ne somme pas la pour ca ...

  • POO avec les classes : La POO avec les classes exclu totalement la notion de prototypes. Les classes sont déclarées en tant que telles : c'est à dire que les classes ne sont pas des fonctions, mais bien des classes à part entière. C'est également dans la déclaration de la classe que nous allons créer un constructeur, les méthodes, les propriétés, etc... Une fois tout cela effectué, la classe peut être instanciée de la même manière, en utilisant l'instruction 'new'. La POO basée sur les classes simplifie également l'héritage, et propose de possibilités supplémentaires que nous verrons plus en détail une peu plus loin dans ce tutoriel.
img:coupe
2. Organisation du code en AS2

:arrow: Chaque classe créé avec AS2 doit se trouver dans un fichier .as qui porte le même nom que la classe. Vous ne pouvez pas déclarer plusieurs classes dans un même fichier .as et si le nom du fichier est différent de celui de la classe, la classe sera tout simplement ignorée (et donc impossible à instancier).

:arrow: Flash MX 2004 offre plusieurs moyens d'accéder aux classes situées dans les .as


  1. Les fichiers .as sont situés dans le même répertoire que le fichier .fla au moment de la compilation.
  2. Les fichiers sont situés dans des sous-répertoires de l'emplacement du fichier .fla et il vous faudra utiliser la commande import dans votre animation pour y accéder. Pour ceux qui connaissent, cela s'aparent au import de Java et au using de C# ... les répertoires deviennent une simulation des packages.
  3. Les fichiers .as sont dans des repertoires qui ont été rajoutés dans les paramètres de publication d'AS2 : Menu Fichier >> Paramètres de publication ... >> Onglet [Flash] >> Bouton [Paramètres...]. Ces répertoires peuvent également être utilisés comme point de départ pour les instructions import.
img:coupe
3. Portage du code de AS1 vers AS2

Les "traductions" de code qui vous sont présentées dans cette partie se limitent à offrir exactement les même fonctionnalités en AS1 que dans leur version AS2. Nous aborderons les spécificités de l'AS2 dans les parties suivantes.

  1. Déclaration d'une classe
    :arrow: Créons une classe en AS1 que l'on va nommer Individu. Nous allons la créer dans _global, afin qu'elle soit accessible de partout. Conformément à ce que nous explique fred dans son tutoriel, nous allons la déclarer comme suit :_global.Individu = function (nom, prenom, date) {
    this._nom = nom;
    this._pre = prenom;
    this._date = date;
    }
    Avec ce code, pour créer un individu, nous allons devoir lui passer un nom, un prénom et une date de naissance. Comme nous en avons l'habitude, le constructeur va s'occuper de créer les propriétés de l'objet.

    :arrow: Pour créer cette même classe en AS2, nous allons utiliser l'instruction 'class' dans un fichier nommé 'Individu.as', et créer son constructeur à l'interieur de la déclaration de classe. Voici ce que cela donne :class Individu {
    var _nom;
    var _pre;
    var _date;

    function Individu(nom, prenom, date) {
    _nom = nom;
    _pre = prenom;
    _date = date;
    }
    }
    Vous remarquerez qu'en AS2, c'est dans la définition de la classe que l'on spécifie quelles sont ses propriétés à l'aide de l'instruction var. La fonction constructeur est une fonction qui porte le même nom que la classe, et elle se content d'assigner les valeurs aux propriétés (mais ne les crée pas). Si l'on n'avait pas déclaré les propriétés, un message d'erreur se serait affiché au moment de la compilation.
    Vous remarquerez également que nous n'avons pas utilisé le sacro-saint ciblage. En effet, il est rarement utilisé dans les langages "classiques", mais peut l'être lorsqu'il y a ambiguité sur la provenance de la variable. Le this existe donc toujours, mais est moins utilisé.

  2. Les méthodes
    :arrow: Aprés avoir créé notre classe Individu en AS1, nous allons vouloir créer ses méthodes. Pour cela, comme expliqué dans la description de la POO "prototype based", nous allons rajouter une fonction au prototype du constructeur aprés la création du constructeur :// Creation du constructeur
    _global.Individu = function (nom, prenom, date) {
    this._nom = nom;
    this._pre = prenom;
    this._date = date;
    }

    // Creation de la methode
    Individu.prototype.afficher = function() {
    trace("L'individu s'appelle " + this._pre + " " + this._nom + " et est né(e) le " + this._age + " ans.");
    }

    :arrow: Voyons maintenant comment créer cette méthode dans le cadre d'un déclaration de classe en AS2. Cette méthode va étre écrite à l'intérieur de la déclaration de classe :class Individu {
    var _nom;
    var _pre;
    var _date;

    // Fonction constructeur
    function Individu(nom, prenom, date) {
    _nom = nom;
    _pre = prenom;
    _date = date;
    }

    // Methode afficher
    function afficher() {
    trace("L'individu s'appelle " + _pre + " " + _nom + " et est né(e) le " + _date + " ans.");
    }
    }

  3. Le addProperty et son remplacant
    :arrow: Dans les langages objets standards, les accés aux propriétés se font généralement uniquement au moyen des méthodes. addProperty est une spécificité de Flash, qui permet plus de confort pour accéder aux propriétés d'une classe. Nous pourrions ici créer une propriété "virtuelle", qui donnerait le sexe de l'individu. Il faut donc créer une méthode qui va retourner le sexe de l'individu. Considérons que pour une raison qui nous est propre, nous voulons stocker le sexe sous la forme d'un chiffre (1 pour les hommes, 2 pour les femmes), mais nous voulons y accéder avec des lettres (M pour les hommes et F pour les femmes). Il nous suffit alors de créer une propriété virtuelle "sexe" :// Creation du constructeur
    _global.Individu = function (nom, prenom, date) {
    this._nom = nom;
    this._pre = prenom;
    this._date = date;
    this._numSexe = 0;
    }

    // Methode qui retourne le sexe de l'individu
    Individu.prototype.getAlphaSexe = function() {
    switch(this._numSexe) {
    case 1 :
    return "M";
    case 2 :
    return "F";
    default :
    return "Inconnu";
    }
    }

    // Methode qui définit le sexe de l'individu
    Individu.prototype.setAlphaSexe = function(lettre) {
    switch(lettre.toUpperCase()) {
    case "M" :
    this._numSexe = 1;
    break;
    case "F" :
    this._numSexe = 2;
    break;
    default :
    this._numSexe = 0;
    }
    }

    // Ajout de la propriété 'sexe'
    with(Individu.prototype) {
    addProperty("sexe",getAlphaSexe,setAlphaSexe);
    }
    Nous avons une propriété "virtuelle" nommée 'sexe', qui va contenir soit "M", soit "F", soit "Inconnu" ... alors que notre objet stocke le sexe de l'individu sous la forme 0, 1 ou 2 ...

    :arrow: Avec AS2, nous allons pouvoir créer des méthodes qui seront considérées directement comme les méthodes get et set d'une propriété "virtuelle". Il suffit de rajouter le mot-clé 'get' ou 'set' avant le nom de la méthode :class Individu {
    var _nom;
    var _pre;
    var _date;
    var _sexe=0;

    // Fonction constructeur
    function Individu(nom, prenom, date) {
    _nom = nom;
    _pre = prenom;
    _date = date;
    }

    // Methode qui retourne le sexe
    function get sexe() {
    switch(_numSexe) {
    case 1 :
    return "M";
    case 2 :
    return "F";
    default :
    return "Inconnu";
    }
    }

    // Methode qui définit le sexe
    function set sexe(lettre) {
    switch(lettre.toUpperCase()) {
    case "M" :
    _numSexe = 1;
    break;
    case "F" :
    _numSexe = 2;
    break;
    default :
    _numSexe = 0;
    }
    }
    }
    Et voila, nous avons effectué exactement la même chose que le code précédent en AS1, c'est à dire que nous pourrons accéder à monIndividu.sexe en utilisant "M" et "F", sauf que le addProperty n'a plus aucune utilité !

  4. L'héritage
    :arrow: En AS1, l'héritage est assez compliqué à mettre en oeuvre, car soit basé sur la méthode de Macromedia qui est assez discutable en terme d'optimisation et de logique (mais que nous allons utiliser ici par soucis de clarté), soit basé sur du code plus complexe qui exploite la chaîne de prototypes. De plus, étant donné que l'héritage se fait aprés la création du constructeur, les chances de se perdre dans le code sont assez importantes. Nous allons voir ici que l'héritage en AS2 est devenu simplissime.

    :arrow: En AS1, pour créer une classe Membre (avec login et mot de passe), qui va hériter de la classe Individu, nous allons écrire un code qui va ressembler à ca :// Constucteur de la classe Individu
    _global.Individu = function(nom, prenom, date) {
    this._nom = nom;
    this._pre = prenom;
    this._date = date;
    }

    // Constructeur de la classe Membre
    _global.Membre = function(nom, prenom, date, login, pass) {
    // On appelle le constructeur de la classe mere
    super(nom, prenom, date);
    this._login = login;
    this._pass = pass;
    }

    // On fait heriter Membre de Individu
    Membre.prototype = new Individu();
    Les instances de la classe Membre héritent ainsi des propriétés/méthodes de la classe Individu, plus celles propres à la classe Membre.

    :arrow: En AS2, l'héritage se fait le plus simplement du monde en utilisant aprés le nom de la classe l'instruction 'extends' suivi de la classe mère. N'oubliez pas que les deux classes sont dans deux fichiers différents !!!// ===== Individu.as =====

    // Declaration de la classe Individu
    class Individu {
    var _nom;
    var _pre;
    var _date;

    // Fonction constructeur
    function Individu(nom, prenom, date) {
    _nom = nom;
    _pre = prenom;
    _date = date;
    }
    }

    // ===== Membre.as =====
    class Membre extends Individu {
    var _login;
    var _pass;

    function Membre(nom, prenom, date, login, pass) {
    super(nom, prenom, date);
    _login = login;
    _pass = pass;
    }
    }
    Et voila, difficile de faire plus simple !!! 8)

img:coupe
4. De nouvelles habitudes avec AS2

:idea: ActionScript 2 introduit des nouveautés qui n'étaient pas accessibles avec AS1, ou alors cela necessitait du code souvent lourd et trés complexe. Ces fonctionnalités deviennent quasi-obligatoires à partir du moment où l'on veut développer un code clair et rigoureux, et se doivent donc d'être utilisées dorénavant. Les exemples donnés uniquement à titre indicatif dans le paragraphe précédent ne les intégrant pas, il ne doit pas être utilisé tel quel. Voici les nouveaux concepts importants d'AS2 :

  1. Le typage "fort"
    :arrow: Le typage fort consiste à donner un type à une variable, et à lui interdire de contenir un type différent que celui qui lui a été imposé. Précédemment, il était tout à fait possible d'assigner un nombre dans une variable, puis à la ligne suivante de lui assigner une chaîne de caractères. Ce qui fait que lire un code oublié, ou fait par quelqu'un d'autre devient assez difficile s'il ne contient pas les typages ...

    :arrow: Concrétement, AS2 permet de typer les variables, les paramètres des méthodes, et la valeur de retour des méthodes. C Le typage se fait en mettant deux points ( : ) aprés la déclaration de la variable, suivi de la classe qui indique le type de la variable. Pour les valeurs retournées par les méthodes, les deux points se mettent aprés la parenthèse de fermeture des arguments. Reprenons la classe Individu créée précédemment :class Individu {
    // La déclaration des propriétés se fait avec le typage
    var _nom : String ;
    var _pre : String;
    var _date : String;
    var _numSexe : Number = 0;

    // Constructeur avec typage des arguments
    function Individu(nom:String, prenom:String, date:String) {
    _nom = nom;
    _pre = prenom;
    _date = date;
    }

    // Methode qui renvoie le sexe avec typage de la valeur de retour
    function get sexe():String {
    switch(_numSexe) {
    case 1 :
    return "M";
    case 2 :
    return "F";
    default :
    return "Inconnu";
    }
    }

    // Le reste du code suit la meme logique ...

    }

  2. Les propriétés et méthodes publiques et privées
    :arrow: Les propriétés et méthodes d'une classe n'ont pas toutes besoins d'être accédées depuis l'extérieur de l'objet. Cela peut parfois être dangeureux d'accéder directement à une propriété, car certaines instructions peuvent ne pas être executées. AS2 offre la possibilité d'empecher l'accés de l'extérieur à certaines propriétés ou méthodes. Ainsi, seules les méthodes de l'objet peuvent accéder à ces dernières.

    :!: Ce concept est trés important, car c'est lui qui garantit l'encapsulation de vos données, ainsi que leur intégrité, surtout lors de développements en travail collaboratif, ou chacun peut-être amené à utiliser les classes d'un autre.

    :arrow: Concrétement, toute propriété/méthode qui ne spécifie pas son "accessibilité" est considérée implicitement comme étant publique. Il est quand même préférable d'éviter ce qui est implicite, est de spécifier clairement ce qui est public et ce qui est privé, excepté pour le constructeur qui est toujours public. Pour cela, on utilise les mots clés 'public' ou 'private'avant la déclaration de la porpriété ou de la méthode. Reprenons notre classe Individu :class Individu {
    // nom, prenom et date sont accessibles
    public var _nom : String;
    public var _pre : String;
    public var _date : String;

    // le sexe en numerique ne peut pas être accédé de l'exterieur
    // On utilisera plutot les methodes get et set ...
    private var _numSexe : Number;

    // Pas besoin de déclaration explicite pour le constructeur
    function Individu(nom:String, prenom:String, date:String) {
    _nom = nom;
    _pre = prenom;
    _date = date;
    }

    // On peut aussi créer des méthodes privées
    private function maMethodePrivee():Number {
    // Cette méthode ne peut être utilisée que par une autre
    // méthode du même objet
    // ....
    }
    }

  3. Les propriétés et méthodes statiques
    :idea: Les propriétés et méthodes statiques sont partagées par toutes les instances d'un même classe. Il n'est même pas nécessaire d'avoir une instance de cette classe pour y accéder. On utilise alors la syntaxe nomDeLaClasse.nomDeLaProp ...

    :arrow: Les méthodes/propriétés statiques sont déclarées au moyen du mot-clé static, situé aprés la déclaration public/private, et avant l'instruction var/function ... Si la déclaration d'un propriété statique est accompagné d'une assignation de valeur, cette assignation n'est executée qu'une fois, au moment de la déclaration de la classe (et non pas à son instanciation).

    :arrow: Une utilisation fréquente des propriétés statiques peut être de compter le nombre d'individus :class Individu {
    // Propriétés contenant le nombre total d'individus
    // Elle est mise à zéro la première fois
    public static nbIndividu:Number = 0;

    // Propriétés non statiques
    public var _nom : String;
    public var _pre : String;
    public var _date : String;
    private var _numSexe : Number;

    // Constructeur de la classe
    function Individu(nom:String, prenom:String, date:String) {
    _nom = nom;
    _pre = prenom;
    _date = date;
    // A la création d'un individu, on augmente la propriété statique de 1
    nbIndividu++;
    }
    }

    On pourra alors accéder à cette propriété soit en appelant nbIndividus depuis l'intérieur d'un méthode (comme cela est fait dans le constructeur, soit en tapant :trace("Nombre d'individus créés : " + Individu.nbIndividus);Dans ce cas précis, le problème est que nous ne pouvons pas réduire le nombre d'individus quand un individu est supprimé, et cela parce qu'il n'est pas possible de créer un destructeur en AS2 ...


  4. Les interfaces
    :idea: Comme expliqué dans le tutoriel sur les concepts de la POO, les interface sont un peu similaires à des classes, sauf que l'on ne peut pas créer d'instance d'un interface, et que les méthoeds ne contiennent pas de code. Elle sont en fait un "contrat", que les classes qui en découlent devront impérativement remplir. Les avantanges des interfaces sont importants lorsqu'il s'agit de travailler en équipe, ou constituent tout simplement une aide à la compilation, afin d'être sur que certaines classes ont été correctement implémentées. Si dans une classe dérivée d'une interface, il manque une méthode de l'interface, le compilateur va signaler une erreur. De la même manière, si la méthode de la classe et celle de l'interface n'ont pas la même signature (nombre et type des arguments + type du retour), le compilateur va également signaler une erreur ...

    :arrow: Un autre aspect des interfaces est que l'on peut créer une classe à partir de plusieurs interfaces. Cela permet de s'assurer que la classe va bien avoir les fonctionnalités qui sont décrites dans les interfaces ...

    :arrow: On ne peut pas tout faire dans une interface AS2 :


    • On ne peut pas déclarer de propriétés
    • On ne peut déclarer que des méthodes publiques
    • On ne peut déclarer de méthodes statiques
    • Une interface ne contient pas de constructeur
    • Une interface ne peut pas implémenter une autre interface
    • Une interface ne peut pas hériter d'une classe

    :arrow: Pour déclarer une interface, on utilise la même syntaxe que pour les classes, sauf que l'on utilise le mot clé interface au lieu de class. Pour dériver une classe d'un interface, on utilise l'instruction implements aprés l'instruction d'héritage. Ensuite, on donne la liste des interfaces implémentées par la classe, separées par des virgules. Concrétement, voici un exemple de deux interfaces (toujours chacune dans un fichier .as différent) et d'un classe qui les implémente. On veut que l'individu soit un humain (il doit donc savoir parler et marcher par exemple), et l'on veut aussi qu'il soit un coureur (il doit donc savoir courir) :// ===== Humain.as ====
    interface Humain {
    public marcher(nbPas:Number);
    public parler(sujet:String):String;
    }

    // ===== Coureur.as =====
    interface Coureur {
    public courir(distance:Number);
    }

    // ===== Individu.as =====
    class Individu implements Humain, Coureur {
    // Declaration des propriétés
    var _nom:String;
    var _pre:String;
    var _date:String

    // Constructeur de la classe
    function Individu (nom:String, prenom:String, date:String) {
    // Code du constructeur
    }

    // On écrit le code des méthodes décrites par les interfaces
    // Interface Humain
    public function marcher(nbPas:Number) {
    // Instructions pour faire marcher l'individu
    }

    public function parler(sujet:String):String {
    // Instructions pour faire parler l'individu
    // sur un sujet donné. La phrase qu'il doit prononcer
    // est retournée par la méthode
    }

    // Interface Coureur
    public function courir(distance:Number) {
    // Instruction pour faire courir l'individu
    // sur une distance donnée
    }
    }
    Dans ce cas précis, si l'on avait pas créé les méthodes marcher, parler et courir, ou si l'on avait créé des méthodes qui possédaient une signature différente de celles données dans les interfaces, le compilateur aurait signalé une erreur.

  5. Les classes "dynamiques"
    :arrow: Comme cela a été dit dans la partie sur les déclarations de classes, il faut déclarer les propriétés d'une classe. Seulement, il peut arriver que l'on ai besoin de rajouter une propriété dans un objet, sans qu'elle ai forcément été déclarée avec la classe. Il faut alors autoriser ce type d'action au moment de la déclaration de la classe, au moyen du mot clé dynamic, situé devant le mot clé class.

    :arrow: Voici un exemple de script qui génère une erreur, et le même script avec une classe dynamique qui ne génère pas d'erreur :// Le constructeur va provoquer une erreur de compilation
    class Individu {
    var _nom:String;
    var _pre:String;
    var _age:Number;

    function Individu(nom:String, prenom:String, age:Number) {
    _nom = nom;
    _pre = prenom;
    _age = age;

    // La propriété _statut n'a pas été déclarée
    // La compilation va retourner une erreur
    if (age < 18) {
    _statut = "mineur";
    } else {
    _statut = "majeur";
    }
    }
    }

    // La classe etant dynamique, le contructeur ne provoque pas d'erreur
    dynamic class Individu {
    var _nom:String;
    var _pre:String;
    var _age:Number;

    function Individu(nom:String, prenom:String, age:Number) {
    _nom = nom;
    _pre = prenom;
    _age = age;

    // La propriété _statut n'a pas été déclarée
    // mais la classe est dynamique, donc pas d'erreur
    if (age < 18) {
    _statut = "mineur";
    } else {
    _statut = "majeur";
    }
    }
    }
    Les propriétés ainsi créées "a la volée" sont automatiquement publiques.


img:coupe
5. Conclusion
:arrow: Comme vous l'avez sûrement constaté, ActionScript 2 apporte non seulement une lisibilité et une organisation bien plus grande de votre code et en facilité grandement la réutilisation.

:arrow: Mais en plus de cela, ses possibilités résolument proche des langages objets standards tels que Java ou C# vous permettent aussi de créer un code plus robuste, qui s'il est bien fait, limite énormément les possibilités d'erreur à l'utilisation des classes que vous avez crées : une fois une classe créée et débuguée en AS2, vous pouvez la réutiliser à volonté, sans vous poser la question de savoir ou se situe l'erreur : elle est dans l'utilisation des classes, et non pas dans le code des classes ...

:arrow: La facilité d'utilisation de l'héritage est un des gros points forts de l'AS2. Pensez-y avant de modifier une classe que vous avez déjà développé : étendez la plutôt au moyen d'une autre classe qui en hérite ...

img:coupe img:remarque msg:tut

FAQ
Cette FAQ sera mise à jour au fur et à mesure des questions sur le forum

  • Que sont devenus mes prototypes ? Puis-je encore les utiliser ?
    Oui ... et non. Les prototypes tels qu'ils sont utilisés couramment (rajouter des fonctionnalités à une classe deja existante) sont toujours disponibles, car AS2 est compatibles avec les objets créés en AS1, ainsi qu'avec les prototypes. Seulement, la nouvelle manière de programmer introduite par AS2 devrait plus vous pousser à faire de l'héritage, c'est-à-dire créer vos propres classes à partir des classes existantes pour y ajouter vos propres fonctionnalités, plutôt que de rajouter vos fonctionnalités à la classe existante. Si ce n'est pas évident pour les classes "visuelles" (MovieClip, TextField, etc...), ca l'est beaucoup plus pour les autres...
  • Dois-je recoder toutes mes classes faites avec AS1 ?
    Non, mais c'est conseillé. En effet, comme dit plus haut, les classes AS1 sont compatibles avec les classes AS2. Seulement, la lisibilité du code risque d'en pâtir fortement. Autant uniformiser votre code le plus possible.
  • Ne serait-ce pas plus pratique de ne faire que des classes dynamiques ?
    Absolument pas. Le but de la déclaration des propriétés est d'être sûr de ne pas surcharger un objet qui n'en a pas besoin. Ne faire que des classes dynamiques reviendrait à refaire de l'AS1, sans aucun contrôle sur les classes.
  • Comment faire pour pouvoir passer n'importe quel type d'objet à une méthode avec le typage fort ?
    Il suffit de faire le typage avec Object. En fait, le typage vérifie si l'argument passé est bien du type demandé ou s'il hérite de ce type.

Interceptions d'erreurs avec Flash MX 2004

Toujours pour Flash Forum, j'ai rédigé un tutoriel sur la manière d'intercepter les erreurs et de créer ses propres erreurs avec le nouveau Flash MX 2004

On constate alors que les instructions pour emettre ou intercepter une erreur envoient en fait des instances de la classe Error ou d'une classe qui en hérite. Mais en fait, il n'existe aucune contrainte en ce qui concerne le type d'objet envoyé ou receptionné par ces instructions. On peut alors apparenter ce système avec la programmation évenementielle. De la a y voir d'autres applications, il n'y a qu'un pas ...

Interception d'erreurs dans Flash MX 2004

Auteur : LAlex
Mail : lalex@flash-forum.net
URL : http://www.flash-forum.net
Date de création : 20/09/2003
Version : Flash MX 2004

Présentation
:idea: ActionScript 2.0 nous permet maintenant d'intercepter une erreur qui aurait pu se produire durant une portion de code, voire même de créer nos propres erreurs, et de réagir différemment selon le type d'erreur. Nous allons ici voir comment s'en servir.

img:coupe
1. Intercepter une erreur

:arrow: Les instructions pour intercepter une erreur sont try, catch et finally. Ces instructions sont utilisées avec des groupes de codes entre accolades { et }, comme on le fait avec les boucles ou les fonctions :

  • try permet de commencer un portion de code dans laquelle on va pouvoir intercepter une erreur.
  • catch commence une portion de code qui va être executée si une erreur est interceptée dans la portion try qui la précède. catch fonctionne un peu comme un evenement, c'est-à-dire qu'elle prend un argument qui est de type Error.
  • finally commence une portion de code qui sera executée quoi qu'il arrive, qu'une erreur soit interceptée ou pas, même si la portion try possède un return, alros que normalement toutes les instructions appellées aprés avoir effectué un return ne sont pas executées.
:arrow: Un bloc try doit être suivi de au moins un bloc catch ou un bloc finally.

:arrow: La classe Error contient un constructeur, deux propriétés et une méthode:

  • Error( [message] ) : le constructeur prend en paramètre (optionnel) le message d'erreur.
  • Error.message : spécifie le message d'erreur, le plus souvent destiné à être affiché.
  • Error.name : Le nom de l'erreur. Ce nom peut servir d'identifiant
  • Error.toString() : Methode qui retourne une représentation de l'erreur sous forme de chaîne de caractères.
:arrow: Voyons un exemple classique d'interception d'erreur dans le cadre d'un entretien d'embauche :// On commence a surveiller les erreurs
try {
   candidat = new Individu("DURAND","Pierre","18/06/1987");
   candidat.getPermisDeConduire();
   candidat.getCurriculum();
   // Les autres instructions a effectuer
   // pour l'entretien
} catch (catchedError : Error) {
   // Si une erreur se produit, elle est tranmise à catch
   // dans le parametre catchedError
   trace(catchedError.toString());
   candidat.allerALaPorte();
} finally {
   // Quoi qu'il arrive, on supprime le candidat
   // a la fin de l'entretien
   delete postulant;
}
Ici, quand on demande son permis de conduire au candidat, comme il est mineur il n'en a pas, et déclenche donc une erreur. Cette erreur est envoyé comme paramètre à l'instruction catch. img:coupe 2. Provoquer une erreur :arrow: Il est fréquent que dans la méthode d'une de nos classes, on puisse dire au code qui utilise cette classe que quelque chose s'est mal passé. Nous allons donc "diffuser une erreur", au moyen de l'instruction throw. Cette instruction "emet" une erreur qui lui est fournie juste aprés. :arrow: Si une instruction throw n'est pas interceptée par un catch, elle se contente d'afficher l'erreur dans la fenêtre de sortie (comme un trace en fait). :arrow: Voyons comment nous pouvons emmettre une erreur personnalisée lorsqu'un candidat n'est pas majeur :// On crée une fonction qui va vérifier si le candidat est majeur
// S'il ne l'est pas, on emet une erreur avec un message personalisé
function verifierMajorite(c:Individu) {
   if (c.age < 18) {
      throw new Error("Ce candidat n'est pas majeur !");
   }
}
// On déclare notre candidat
var candidat:Individu;
// On commence a surveiller les erreurs
try {
   candidat = new Individu("DURAND","Pierre","18/06/1987");
   verifierMajorite(candidat);
   // La suite de l'entretien
} catch (catchedError : Error) {
   // S'il y a erreur, on affiche le message
   trace(catchedError.message);
}
img:coupe 3. Réagir selon le type d'erreur :arrow: Les instruction try ... catch permettent également de réagir différemment selon le type d'erreur qui survient. Cela est possible grâce aux possibilités de typage fort d'ActionScript 2, en créant plusieurs blocs d'instructions catch qui vont attendre chacun un type d'erreur différent. Cela va nécessiter également de créer des classes qui héritent de la classe Error, et vont posséder leurs propres spécificités. :arrow: Nous allons ici créer plusieurs classes d'erreurs. Une sera utilisée si le candidat n'st pas majeur, l'autre si le candidat n'a pas d'expérience. Toutes ces classes vont hériter de la classe Error. Les classes sont bien entendus dans des fichiers .as différents. Pour plus d'infos sur les classes AS2 et l'héritage, voir le tuto [tuto de AS1 à AS2]// ===== ErreurTropJeune.as ====
class ErreurTropJeune extends Error {
   var message:String = "Ce candidat est trop jeune";
}
// ===== ErreurTropNovice.as =====
class ErreurTropNovice extends Error {
   var message:String = "Ce candidat n'a pas assez d'experience";
}
// ===== Animation principale =====
// Function qui verifie si le candidat est majeur
// Si ce n'est pas le cas, diffuse l'erreur personalisée
function verifierMajorite(c:Individu) {
   if (c.age < 18) {
      throw new ErreurTropJeune();
   }
}
// Function qui vérifie si le candidat est suffisemment expérimenté
// Si ce n'est pas le cas, diffuse l'erreur personnalisée
function verifierExperience(c:Individu) {
   if (c.experience < 5) {
      throw new ErreurTropNovice();
   }
}
// On déclare notre candidat
var candidat:Individu;
// On commence a surveiller les erreurs
try {
   candidat = new Individu("DURAND","Pierre","18/06/1987");
   verifierMajorite(candidat);
   verifierExperience(candidat);
   // La suite de l'entretien
} catch (catchedError : ErreurTropJeune) {
   // S'il est trop jeune, on lui fait quitter le bureau
   candidat.allerVersLaPorte();
} catch (catchedError : ErreurTropNovice) {
   // S'il manque d'expérience, on le forme
   candidat.allerEnFormation();
} finally {
   // Quoi qu'il arrive, on arrete l'entretien
   delete candidat;
}

img:coupe
4. Conclusion

:arrow: Aprés avoir vu de quoi il en retourne, à quoi sert la gestion des erreurs ? Tout simplement à savoir quand quelque chose ne va pas ... On pourrais faire une vague comparaison avec le programmation évenementielle, sauf que normalement ca ne se déclenche que quand quelque chose ne va pas ... On peut ainsi intercepter les erreurs qui interviennent dans une classe que l'on ne connaît pas, ou un composant compilé, sur lequel on ne peut pas intervenir au niveau du code source.

:arrow: En poussant la chose un peu plus loin, on constate que l'instruction throw peut envoyer autre chose que des messages d'erreurs, et qu'en utilisant le typage avec l'instruction catch, on peut recevoir n'importe quel type d'objet. A partir de la, il est facile de trouver des utilisations diverses, qui n'ont même rien à voir avec une interception d'erreur ... ;)

img:coupe
img:remarque msg:tut

1 septembre 2003

Article sur les bases de la POO

Voici de l'article que j'ai rédigé pour Flash Forum concernant les concepts de base de la Programmation Orientée Objet, mis à la portée des programmeurs intermédiaires qui veulent se mettre à la POO.

Cet article aborde le POO d'un point de vue générique, sans aucune implémentation dédiée à un langage particulier. Bien évidemment, les lecteurs principaux seront des programmeurs Flash, qui pourront mettre ces concepts en application avec ActionScript.

La programmation Orientée Objet (POO)

Auteur : LAlex
Mail : lalex@flash-forum.net
URL : http://www.flash-forum.net
Date de création : 31/07/2003
Version : Flash 5 / MX

Présentation

:idea: De plus en plus dans le cadre de la programmation en Flash, on entend parler de POO, soit la Programmation Orientée Objet. Pour beaucoup de débutants ou de non programmeurs, ce terme est assez abstrait. Ce tutoriel se propose de vous décrire le pourquoi de la programmation objet, ainsi que ses bases et règles qui lui sont propres.
:idea: Ce tutoriel comporte uniquement les bases théoriques de la POO, et ne contient pas de code. Des mises en pratiques seront faites dans d'autres tutoriaux, afin de clarifier chaque aspect distinct de la POO.

1. Principe de la POO

:arrow: Le but de la programmation Objet est de se rapprocher le plus possible d'une représentation de la réalité en programmation, en considérant que tout objet peut être classé dans une catégorie qui a ses caractéristiques et ses possibilités propres. En effet, tout objet qui nous entoure peut être "classé" dans une categorie qui fait elle-même partie d'autres catégories.

    http://www.flash-forum.net/imgtut/poo/categories.gif" border="0" align="center"/>

Dans l'exemple précédent, votre mobile fait partie de la catégorie "Téléphone portable", qui elle-même fait partie de la catégorie "Téléphone", faisant partie de la catégorie "Outil de communication téléphonique", faisant partie de la catégorie "Outils de communication", etc.... Tandis qu'un fax est "Outil de communication téléphonique", mais pas un "Téléphone" et encore moins un "Téléphone portable".

img:coupe
2. Vocabulaire


  • Classe : On appellera "classe" ce qui est nommé plus haut comme étant une "catégorie". Une classe est en fait la description des possibilités offertes par un objet qui va appartenir à cette catégorie. Dans l'exemple précédent, on pourra considérer qu'il existe une classe "Mobile".
  • Instance : On appellera "instance" tout objet créé à partir d'une "classe". Par exemple, mon téléphone portable est une instance de la classe "Mobile". Un objet est toujours l'instance d'une classe.
  • Attribut : On appellera "attribut" toute valeur qui s'attache à décrire l'objet. Par exemple, si mon téléphone est bleu, on peut considéré que son attribut 'couleur' a pour valeur 'bleu'.
  • Méthode : On appellera "méthode" toute série d'instructions qui permettent d'agir sur l'objet, ou de faire agir l'objet lui même. Par exemple, on peut considérer que pour allumer mon téléphone, je fais appel à la méthode 'allumerTelephone'.
  • Propriété : D'un point de vue purement sémantique, les propriétés d'un objet sont l'ensemble de ses attributs et méthodes. Un abus de langage quasi-systématique consiste à appeler "propriétés" uniquements les attributs de l'objet. Pour plus de simplicité, nous utiliserons cet abus de langage dans le reste du tutoriel.:oops:
  • Interface : Une interface est un peu comme une classe, sauf que l'on ne peut pas créer d'objet à partir d'une interface, et qu'elle ne contient pas de code "à executer". Elle sera utilisée comme base pour créer des classes. Elle s'attache à décrire quelles vont être les actions qu'une classe va effectuer, et l'on devra écrire le code qui va permettre d'effectuer ces actions dans la classe. Créer une classe à partir d'un interface est aussi appelé "implémenter une interface".

img:coupe
3. Notions de bases

  • L'encapsulation
    :arrow: L'encapsulation correspond à appliquer le principe de la "boîte noire" aux objets que l'on crée. Cela consiste à demander à un objet d'effectuer une action, sans se préoccuper de comment il l'effectue. Elle permet également de faire des contrôles de sécurité, et de ne pas effectuer les actions demandées si une condition particulière n'est pas respectée. Par exemple, pour allumer notre téléphone portable, on se contente d'appuyer sur la touche [Power] : peu nous importe ensuite de savoir quelle est la procédure interne suivie par le téléphone pour s'allumer... on désire juste savoir quand le téléphone est prêt à être utilisé.

    :arrow: Pour une action à effectuer, il va falloir déterminer quels vont être les paramètres en entrée, quels vont être les paramètres en sortie, et quels vont être les actions effectuées sur l'objet lui-même, pour ensuite les mettre à disposition de l'utilisateur. Pour notre méthode 'allumerTelephone' :

    • le paramètre d'entrée est l'appui sur la touche [Power].
    • les actions que l'objet effectue sur lui-même sont
      • allumer l'écran
      • charger le répertoire
      • charger la configuration
      • charger le menu
      • etc...
    • le paramètre de sortie est que le téléphone est prêt (cela peut être invisible, ou un bip, ou autre chose)

  • Le polymorphisme
    De manière générale, il existe trois types de polymorphisme :
    • Le polymorphisme "paramétrique" : il est également appelé "généricité". Cela consiste à effectuer des actions différentes selon le nombre et le type de ses paramètres (le nombre et le type des paramètres est appelé 'signature'). Sur mon téléphone portable (dans la plupart des portables d'ailleurs), si je tape un numéro de téléphone complet, et que j'appelle la méthode 'appelerDestinataire', il va composer le numéro. Si par contre, je rentre '1#' puis que j'appuie sur 'appelerDestinataire', il va chercher le premier numéro dans mon répertoire, puis le composer. Donc, la réaction de la méthode 'appeler' est différente (bien que similaire) selon le paramètre qui lui est donné.
    • Le polymorphisme de "surcharge" (ou "ad hoc" ou "overloading" en anglais) : cela consiste à avoir la même méthode avec la même signature pour des objets de type analogue, voire complétement différents. Si mon téléphone est de marque 'X', il possède une méthode 'appelerDestinataire' et un micro pour parler. Un interphone d'immeuble possède aussi la méthode 'appelerDestinataire' et un micro. Je ne me préoccupe donc pas du type d'objet dont je me sers, il me suffit d'utiliser la méthode 'appelerDestinataire' et d'utiliser le micro pour parler.
    • Le polymorphisme "d'héritage" (ou "overriding" en anglais). Ce polymorphisme est issu (comme son nom l'indique ;)) de l'héritage. Cela consiste à faire hériter plusieurs classes d'une même classe 'mère', et de redéfinir une méthode pour chaque classe. Par exemple, les téléphones de différentes marques n'ont pas la même structure interne. Un téléphone de la marque 'X' va par exemple afficher un petit logo quand on utilise 'appelerDestinataire', tandis qu'un autre va allumer l'écran. Chaque contructeur a donc créé sa propre classe "MobileX" ou "MobileY" héritée de la classe "Mobile", puis réécris par dessus la méthode 'appelerDestinataire' pour y integrer ses propres spécificités.
      NB : La notion d'héritage est abordée plus loin dans ce tutoriel.

img:coupe
4. Les relations entre objets

  • L'héritage

    :arrow: L'héritage est une relation entre objets de type "est un". Elle représente le fait, pour une classe, d'être une sous-partie d'une autre classe. C'est le même principe que notre exemple : le téléphone portable "est un" téléphone, et le téléphone "est un" outil de communication téléphonique.

    :arrow: Une "sous-classe" (également appelée "classe fille") hérite des possibilités et des propriétés de sa "super-classe" (également appelée "classe mère"). Il peut arriver que la "classe fille" puisse effectuer des actions supplémentaires ou comporter des propriétés supplémentaires. Il peut également arriver que pour effectuer une même action que sa "classe mère", une "classe fille" ait besoin d'exécuter des instructions supplémentaires. On va alors redéfinir une méthode de cette classe qui va executer ses instructions propres, plsu éventuellement les instructions de la "classe mère". Par exemple, la classe "Mobile" hérite de la classe "Telephone", et récupère donc les porpriétés 'touche0', 'touche1', 'touche2', etc... mais possède également en plus une touche 'on/off'.

    :arrow: Dans d'autres langages, il existe la possibilité de faire de l'héritage multiple, mais pas dans Flash (du moins jusqu'à la version MX). Cela consiste tout simplement à hériter de plusieurs classes : une orange "est un" fruit, mais une orange "est une" sphère aussi. Elle a donc les propriétés d'un fruit (couleur, prix au kilo ...) et celles d'un sphère (rayon). L'héritage multiple suscite un débat important dans la communauté des développeurs. Un language comme C++ permet de créer des classes à partir de plusieurs classes (comme décrit ci-dessus), mais les classes de nouveaux langages comme JAVA ou C# permettent uniquement de faire de l'héritage multiple à partir de plusieurs interfaces, mais ne peuvent hériter que d'une seule classe, cela évitant des conflits de "code" entre deux méthodes ayant le même nom.

  • La délégation

    :arrow: La délégation est une relation de type "a un" entre deux objets. Elle intervient lorsque deux objets collaborent entre eux. Ce type de relation se décompose en trois parties :


    • L'association : elle consiste à une simple relation de collaboration entre deux objets. Chaque objet existe de manière totalement indépendante, et n'a pas forcément besoin de l'autre pour "vivre". La relation entre un téléphone portable et son kit main-libre est une relation d'association.
    • L'agrégation : elle se distingue de l'association par le fait qu'un objet est une partie de l'autre. Ainsi, un bouton sur un téléphone portable a une relation d'agrégation avec le téléphone. Si une certaine touche n'est pas présente, le téléphone peut néanmoins fonctionner, en paliant ce manque par des combinaisons d'autres touches.
    • La composition : elle est une sous-partie de la relation d'agrégation. Elle représente une relation étroite entre les deux objets : l'objet "père" ne peut fonctionner sans l'objet "fils". L'objet "batterie" ou "micro" d'un téléphone portable ont une relation de composition avec le téléphone, car le téléphone ne peut pas fonctionner sans.

    NB : Dans le doute du type de relation à utiliser, il vaut mieux utiliser l'association

img:coupe
5. Petite mise en pratique

:idea: Nous allons ici essayer de mettre en pratique ces quelques notions par un exemple simple, qui diffère de notre téléphone portable qui n'a plus beaucoup de batteries tellement nous l'avons utilisé. :D Nous utiliserons un "pseudo-langage" à priori possible à comprendre par le plus grand nombre dont voici les quelques conventions :


  • le terme "classe" signifie que l'on crée une classe. 'classe MaClasse { ... }' signifie "je crée une classe nommée MaClasse dont les instructions sont entre les accolades".
  • Pour dire qu'un objet est d'une certaine classe, nous noterons 'monObjet : MaClasse', ce qui voudra dire "monObjet est une instance de MaClasse"
  • Les méthodes sont declarées sous la forme 'monObjet.maMethode( parametre1, parametre2)', ce qui veut dire "je crée la méthode maMethode de l'objet monObjet qui va prendre les paramètres parametre1 et parametre2". Des parenthèses vides signifient que la méthode ne prend aucun paramètre.
  • L'affectation d'une valeur à une propriété se fait sous la forme 'maPropriete = maValeur'. Cela se traduit par "Je donne à maPropriete la valeur maValeur".
  • L'appel à une méthode se fait sous la forme 'monObjet.maMethode(param1, param2)', ce qui se traduit par "J'appelle la méthode maMethode de monObjet avec les paramètres param1 et param2".
  • Les lignes commencant par '//' sont des lignes de commentaires, et n'interviennent pas dans l'exécution du code.

:arrow: Le but est de créer un ensemble de classes destinées à décrire une voiture de marque 'MediaBox'.

  • Tout d'abord, commençons par créer une classe 'Vehicule'. Nous allons considérer qu'un véhicule comporte un moteur et des roues toutes identiques. Dans la classe véhicule, nous allons créer une propriété 'modeleDeRoue', qui constituera le modèle des roues du véhicule. En effet, considérant que toutes les roues sont identiques, il ne sert à rien de créer plusieurs roues qui seront exactement les mêmes ... :)
    Vous me direz : "Pourquoi pas un volant ?". Tout simplement parce que le volant est quelque chose de spécifique aux voitures, camions etc... Alors qu'une moto est un véhicule, mais n'a pas de volant. Nous pourrions considérer qu'un bateau est un véhicule, mais par soucis de simplification, nous allons ne pas en tenir compte ... ;)
    En fait, la question à se poser lors de la création d'une classe est : "Toutes les instances de cette classe ont-elles cette propriété ?", ou "Toutes les instances de cette classe peuvent-elles effectuer cette action ?".
    Voici donc une classe 'Vehicule' :classe Vehicule {
       // Proprietes
       moteurDuVehicule : Moteur
       modeleDeRoue : Roue
       // Methodes
       allumerMoteur ( clef )
       eteindreMoteur ( )
    }
  • Nous allons ensuite créer une classe 'Voiture', qui "est un" véhicule. Elle va donc hériter de la classe 'Vehicule'. En plus des possibilités d'un véhicule, la voiture possède un volant et un nombre de roues défini. Nous allons donc rajouter ce volant à la classe Voiture . Ce volant offrant la possibilité de tourner, nous allons donc créer une méthode 'tourner' qui va prendre en paramètre la direction dans laquelle on veut tourner.classe Voiture herite de Vehicule {
       // Proprietes
       volant : Volant
       nombreDeRoues : Nombre
       // Initialisation des proprietes
       nombreDeRoues = 4
       // Methodes
       tourner( direction )
    }
    Vous remarquerez sûrement que l'on ne déclare pas de roue ni de moteur à cette voiture. Elle n'en a pas besoin, car héritant de la classe 'Vehicule', elle hérite automatiquement des ses propriétés et méthodes. Donc, sur une instance de la classe 'Voiture', nous allons pouvoir appeler les méthodes 'allumerMoteur' et 'eteindreMoteur'...
  • Voila enfin le moment de créer notre classe proprement dite : la Voiture MediaBox !!! 8) L'équipe de MediaBox n'étant pas avare de ses efforts, elle vous offre en exclusivité la possibilité de voler !!! (qui a dit Taxi 2 ?!? ....... J'ai les noms, je vous préviens !!!:twisted:). Il va donc falloir lui rajouter des ailes, et lui permettre de plier/déplier les ailes, et de décoller (pour l'atterrissage, vous vous débrouillerez) :classe VoitureMediaBox herite de Voiture {
       // Proprietes
       ailesDeLaVoiture : Aile
       // Methodes
       deplierLesAiles ()
       plierLesAiles ()
       decoller ()
    }
    Vous noterez ici un des avantages énormes de la POO, c'est que la méthode 'plierLesAiles' peut vérifier si l'on est en vol ou pas avant de les replier effectivement. Si c'est le cas, elle ne repliera pas les ailes et pourra retourner une notification sur le fait qu'elle ne l'ait pas fait, voire donner la raison pour laquelle elle ne l'a pas fait. A l'utilisation d'une VoitureMediaBox, vous ne vous poserez pas ce genre de question, étant donné que l'objet le gère tout seul ! De la même manière que pour la classe 'Voiture', nous n'avons pas à nous soucier des méthodes et propriétés qui ne font pas partie des spécificités de notre 'VoitureMediaBox'.
  • Voici maintenant le moment d'acheter une voiture MediaBox. Pour cela, nous allons créer une instance de la classe 'VoitureMediaBox', puis l'utiliser pour faire un petit tour du monde :// Mon objet 'maVoitureMB' devient une instance de 'VoitureMediaBox'
    maVoitureMB : VoitureMediaBox
    // Je demarre ma voiture, je déplie les ailes et je la fais décoller
    maVoitureMB.demarrerMoteur( "123456")
    maVoitureMB.deplierLesAiles()
    maVoitureMB.decoller()

img:coupe
6. Conclusion

:idea: Vous aurez certainement compris pourquoi la programmation Orientée Objet est utilisée maintenant par la quasi-totalité des langages actuels. Elle offre une souplesse et surtout une possibilité de réutilisation très importante. Elle permet de n'écrire qu'une seule fois le même code, et de réutiliser facilement son propre code ou un code provenant d'un autre programmeur. C'est donc un choix évident pour les équipes de développement qui travaillent ensemble. De par sa forte relation avec la représentation de la réalité, la POO est aussi plus naturelle à l'utilisation.

:arrow: Il ne reste donc plus qu'à lui trouver des inconvénients, mais je n'en vois qu'un : les codes en POO sont plus lents, car les instructions unitaires sont appelées après un passage par de multiples couches, qui prennent évidemment plus de temps qu'un appel direct à l'instruction.

:arrow: Lors du travail en équipe sur une même application, il faut faire une confiance aveugle à la personne qui a développé les classes que vous utilisez !!! En effet, si une erreur s'est glissée dans l'une de ces classes, elle peut arriver à un dysfonctionnement complet de l'application. En Flash, les accès à la mémoire de l'ordinateur étant correctement protégés, les gênes provoquées sont relativement minimes ...

:arrow: Un autre "inconvénient" qui n'en est pas un, est que développer en POO nécessite une phase d'analyse bien plus importante, car si elle est bâclée, elle peut nécessiter une réécriture de tout votre code en plein milieu du développement, ou pénaliser les développeurs qui vont utiliser vos classes.

:!: Si vous voulez vous y mettre tout de suite, une tutoriel de fred sur la création d'une classe simple est disponible : http://www.flash-forum.net/viewtopic.php?t=14295

img:coupe
img:remarque msg:tut

8 août 2003

Exposé sur les conventions de présentation du code ActionScript

Voici de l'article que j'ai rédigé pour Flash Forum concernant certaines conventions utilisées par les programmeurs pour rendre leur code lisible.

Je me suis inspiré pour ca tout d'abord de mon expérience de programmeur, puis du simplebon sens, et ensuite de conseils avisé d'autres programmeurs sur de petits oublis. Cet article donne donc la plupart des conseils nécessaires pour produire un code clair, et donc facile à maintenir.
Car on ne le dira jamais assez, le premier bénéficiaire d'un code propre, c'est bien le programmeur lui-même !!!

Les conventions de présentation du code

Auteur : LAlex
Mail : lalex@flash-forum.net
URL : http://www.flash-forum.net
Date de création : 31/07/2003
Version : Flash 5 / MX


Présentation

"Un bon code est un code que l'on peut comprendre sans connaître le langage utilisé ..."

:idea: Le but de ce tutoriel est de vous présenter des conventions de codage à utiliser avec ActionScript. Elles sont utilisées dans leur plus grande partie par les programmeurs du monde entier, sur la plupart des langages informatiques. Ces conventions n'ont rien d'obligatoires, elles permettent seulement de vous faciliter la relecture de votre code, et de permettre à n'importe quel autre programmeur ActionScript (ou autre) de le comprendre facilement. Voici les différents points à prendre en compte :

  1. Les conventions de nommage des variables
  2. Les conventions de nommage des classes
  3. Les conventions de nommage des méthodes
  4. Les conventions d'indentation du code
  5. Les conventions de ciblage des variables
  6. Les conventions de commentaires
  7. Conseils supplémentaires
  8. Les considérations sur le poids des animations
  9. Conclusion
  • Annexe A : Les mots réservés
  • Annexe B : Les mots déconseillés

1. Les conventions de nommage des variables

:!:ATTENTION:!: N'UTILISEZ JAMAIS D'ACCENTS DANS VOS NOMS DE VARIABLES !

:arrow: Les noms de variables commencent par une minuscule. Les différents "mots" qui composent le nom de cette variables commencent eux par une majuscule. L'underscore ( '_' ) est parfois utilisé comme séparateur de "mots", mais rend les noms de variables plus longs. Cette pratique est maintenant assez rare : // Variable correctement ncaommée
var numeroDeDossard = 10;
// Variable bien nommée, mais peu pratique
var numero_de_dossard = 10;
// (!) Variable mal nommée
var numerodedossard = 10;
:arrow: Choisissez des noms de variables explicites, qui décrivent le rôle de la variable ://Variable correctement nommée
var jourDeLaSemaine = "Lundi";
// (!) Variable mal nommée
var toto = "Lundi"
:arrow: Excluez les noms tels que 'toto', 'tata', 'titi'. Les noms de variables en une seule lettre sont à exclure également (sauf 'x' et 'y' pour des coordonnées...). Une entorce à cette règle est souvent pratiquée pour les boucles 'for'. Beaucoup de programmeurs utilisent les noms de variable 'i', 'j' etc... pour leurs compteurs de boucle : for (var i=0 ; i<10 ; i++) {
   trace("Compteur de boucle : " + i);
}
:arrow: Dans la plupart des langages traditionnels, chaque variable doit avoir été déclarée avec son type. Pour pouvoir identifier le type de ces variables, les programmeurs font commencer les noms de variables par une abréviation du type. Ainsi, les variables numériques commencent par 'int', les tableaux par 'ar', les chaînes de caractères par 'str', les booléens par 'b' etc... Le typage dans Flash n'étant pas un "typage fort", peu de codeurs ActionScript utilisent cette convention.var intNumeroDeDossard = 10;
var arJoursDeLaSemaine = new Array("Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim");
var dteAujourdhui = new Date();
var bEstArrive = true;
:arrow: Certains usages veulent aussi que les membres d'une classe soient préfixés avec m_ ... Cela permet de différencier, dans la méthode d'une classe, l'accés aux propriétés de l'objet et l'accés aux variables passées en paramètres ou aux variables locales. Tout comme la précédente, cette convention est peu utilisée en Flash, ou fait place à un underscore tout seul au début de la propriété (comme '_x' et '_y' dans la classe MovieClip)._global.Course = function(nbCoureurs) {
   this.m_arCoureurs = new Array(nbCoureurs);
}
2. Les conventions de nommage des classes :!:ATTENTION:!: N'UTILISEZ JAMAIS D'ACCENTS DANS VOS NOMS DE CLASSES ! :arrow: Les mêmes règles que les noms de variables s'appliquent pour les noms de classes, sauf que les noms de classes commencent par une majuscule. :arrow: Certains programmeurs (comme fred : cf tuto de création des classes) commencent systématiquement les noms de classes avec un 'C' majuscule. Quel que soit le choix que vous faites, les deux solutions sont lisibles et comprises par les programmeurs.// Classe correctement nommée
_global.Course = function(nbCoureurs) {
   this.arCoureurs = new Array(nbCoureurs);
}
// (!) Classe mal nommée
_global.coureur = function(age) {
   this.ageCoureur = age;
}
3. Les conventions de nommage des méthodes :!:ATTENTION:!: N'UTILISEZ JAMAIS D'ACCENTS DANS VOS NOMS DE METHODES ! :arrow: Les noms de méthodes commencent par des minuscules (en fait, les méthodes ne sont que des variables de type 'Function'). De la même manière, donnez un nom explicite à vos méthodes dans le création de vos classes. Le nom de la méthode doit expliquer ce qu'elle fait. Les conventions de nommage des variables s'appliquent pour les arguments. :arrow: Utilisez des verbes à l'infinitif pour décrire les actions. Utilisez les verbes 'pouvoir', 'avoir' ou 'être' à la troisième personne du singulier et au présent pour les méthodes retournant des valeurs booléennes :Coureur.prototype.verifierDopage = function() { ... }
Coureur.prototype.peutBoire = function() { ... }
Coureur.prototype.aFaim = function() { ... }
Coureur.prototype.estMajeur = function() { ... }
4. Les conventions de ciblage des variables :idea: Cette convention est issue directement de Flash, et ne concerne que rarement d'autres languages. :arrow: En Flash, essayez de systématiquement cibler vos variables. Il est conseillé de cibler même les variables globales (avec '_global'), ou les propriétés d'un objet (avec 'this'). Le ciblage implicite de Flash peut être bien pratique, mais un programmeur qui va regarder votre code, n'a que le ciblage pour savoir d'où vient votre variable. Il saura alors que les variable ciblées sur 'this' sont des propriétés (ou variables de l'objet en cours), que les variables ciblées sur '_global' sont des variables globales, et que les autre sont des variables locales. :arrow: Les seuls cas ou le ciblage n'est pas recommandé sont :

  • pour les variables locales, déclarées avec var, ou les arguments d'une fonction car de toutes façons, on ne peut pas les cibler facilement.
  • pour les classes. Dans la plupart des cas, les classes sont déclarées dans '_global'. En effet, l'opérateur 'new' utilisée permet de repérer facilement le fait qu'il s'agisse d'une classe : ne pas utiliser le _global ne nuit donc pas à la compréhension du code.
:arrow: N'utilisez que du ciblage relatif, c'est a dire qui part de l'endroit ou vous êtes. Proscrivez dans la mesure du possible les '_level' et '_root'. Utiliser ces deux ciblages revient à se bloquer toute possibilité de ré-utiliser votre code à un autre endroit que celui pour lequel vous l'avez prévu au départ (ce qui fini la plupart du temps par arriver). 5. Les convention d'indentation du code :idea: L'indentation est le fait d'introduire des décalages au début des lignes (tabulations). L'éditeur ActionScript de Flash effectue l'identation tout seul, et comporte même un outil qui permet de la faire automatiquement (en partie). Si vous n'utilisez pas cet outil, ou si vous utilisez d'autres éditeurs (comme UltraEdit par exemple), ou plus simplement si vous utilisez Flash5, il existe quelque règles. :arrow: Les blocs de code entre des accolades '{' et '}' sont décalées d'un cran :Coureur.prototype.recupererCategorie = function() {
   if (this.ageCoureur > 45) {
      return "Vétéran";
   } else {
      return "Senior";
   }
}
:arrow: Certains programmeurs mettent un retour à la ligne avant les accolades d'ouverture '{'. C'est un choix personnel de chacun, car certains trouvent cela plus lisible, d'autres non. :Coureur.prototype.recupererCategorie = function()
{
   if (this.ageCoureur > 45)
   {
      return "Vétéran";
   }
   else
   {
      return "Senior";
   }
}
:arrow: Pour l'instruction particulière 'switch', effectuez aussi un décalage pour les instructions situées aprés un 'case'. Mettez également un seul 'case' par ligne :Course.prototype.peutParticiper = function(coureur) {
   if (coureur.recupererCategorie == "Vétéran") {
      switch(this.difficulte) {
         case "facile" :
         case "normal" :
            return true;
            break;
         case "difficile" :
            return false;
            break;
         default :
            return false;
            break;
      }
   } else {
      return true;
   }
}
6. Les conventions de commentaires :idea: Les commentaires sont une des bases les plus importantes pour la compréhension d'un code. Bien qu'un code proprement écrit soit plus facile à comprendre, chaque programmeur garde sa propre logique, qui n'est pas forcément celle des autres. Pour qu'un code soit compréhensible, il faut donc l'expliquer. Pour écrire un commentaire sur une ligne, utilisez le "double slash" ( '//' ) et pour commenter un bloc de texte, encadrez le par '/*' et '*/' :// Ceci est une ligne de commentaire
/* Ceci est un bloc de commentaire
Il peut comporter plusieurs lignes */
:arrow: En POO, certaines normes existent concernant les classes et méthodes :
  • Pour les classes, dans le commentaire précédant la déclaration, spécifiez :
    • Le nom de la classe
    • Ce qu'elle contient, et ce à quoi elle sert
    • Son(ses) auteur(s)
    • Les remarques et spécifités à savoir sur la classe
  • Pour les méthodes, vous devez donner :
    • L'action qu'elle effectue
    • Ce qu'elle retourne, et de quelle type est la valeur de retour
    • Chacun de ses paramètres, avec
      • Son type
      • Son nom
      • Sa description
      • S'il est facultatif ou non (si rien n'est spécifié, il est considéré comme obligatoire)
/*****************************
* classe : Course            *
******************************
* Auteur : LAlex             *
*                            *
* Contient une course et     *
* les méthodes pour la gérer *
*                            *
* Attention : ne marche que  *
* pour les courses a pied.   *
******************************/

/*****************************
* Contructeur Course         *
******************************
* Constructeur de la classe  *
* Course                     *
*                            *
* - retour : aucun           *
*                            *
* Paramètres:               *
* - nbKilometres : numerique *
*   longueur de la course    *
*****************************/

_global.Course = function(nbKilometres) {
   this.distance = nbKilometres;
}
:!:ATTENTION:!: Le constructeur d'une classe est aussi une méthode, il faut donc le commenter en conséquence ! :arrow: Evitez de commenter chaque ligne, ou les lignes trop élémentaires. Commentez une action, même si elle comporte plusieurs instructions. Ne faites pas pour autant un roman pour expliquer le 20 prochaines instructions. Un commentaire sur 3 ou 4 instructions qui vont LOGIQUEMENT ensemble est suffisant.Coureur.prototype.verifierVitesse = function() {
   // Si le coureur est trop lent, on l'élimine
   if (this.vitesseMoyenne < 10) {
      this.eliminer();
   // Sinon, si il va trop vite, on fait un test anti-dopage
   // et on l'élimine s'il est positif
   } else if (this.vitesseMoyenne > 60) {
      if (this.verifierDopage()) {
         this.eliminer();
      }
   }
}
:!: Le commentaire doit dire ce que ne dit pas le code : parlez de l'action que vous faites, pas des variables que vous manipulez.// Bien commenté
Course.prototype.modifierLongueur = function(longueur) {
   // Modifie la longueur de la course avec la valeur passée en paramètre
   this.longueurCourse += longueur;
}
// (!) Mal commenté
Course.prototype.modifierLongueur = function(longueur) {
   // Rajoute 'longueur' à 'this.longueurCourse' => Sans blagues ?!?
   this.longueurCourse += longueur;
}
:arrow: Aérez un maximum votre code. N'hésitez pas à placer des lignes vides entre des blocs d'instructions différents, même s'il ne comportent pas d'accolades. Par exemple, un retour à la ligne juste avant un commentaire permettra de le différencier du code qui est situé juste avant. 7. Conseils supplémentaires :idea: Rassemblez les fonctionnalités analogues au même endroit. Ne déclarez pas une classe à un endroit pour créer ses méthodes à un autre endroit. :idea: Utilisez toujours la même langue dans votre code. Si une classe s'appelle 'Coureur', ne faite pas une méthode qui s'appelle 'canRunNaked'... et inversement. Bien entendu, si vous voulez diffuser votre code sur des sites non-francophones, l'anglais reste la meilleure solution! ;) :idea: Evitez de mettre plusieurs instuctions séparées par des points-virgules sur une seule ligne. :idea: Evitez les écritures condensées :this.ageCoureur > 75 ? trace("Trop Vieux") : trace("OK");
maVariable1 = maVariable2 = maVariable3;

8. Considérations sur le poids des animations

:arrow: Les conventions précédentes ne prennent pas en considération un problème mineur de Flash, qui est le fait que Flash conserve les noms de variables. Donc un nom de variables long va prendre plus de place (en terme de poids du fichier) qu'un nom de variable d'une ou deux lettres.

:arrow: La meilleure chose à faire si l'on veut optimiser au maximum est de faire un code avec des noms de variable "conventionnés", puis de le dupliquer et d'utiliser à bon escient le Chercher/Remplacer pour modifier les noms de variables (Attention : une mauvaise utilisation du Chercher/Remplacer peut détruire tout votre code).

9. Conclusion

:arrow: Comme je vous l'ai précisé au début de ce tutoriel, aucune de ces conventions n'est indispensable au bon fonctionnement de votre code, mais vous constaterez vite, si vous les utilisez, que cela constitue un gain de temps non négligeable, tant pour vous que pour ceux qui voudront lire votre code (par exemple les membres de Flash Forum qui veulent répondre à une de vos question ;))

Annexe A : Les mots réservés

:arrow: Certains mots utilisés en interne par Flash ne peuvent pas être utilisés comme noms de variables, classes ou méthodes :

  • add
  • and
  • break
  • case
  • continue
  • default
  • delete
  • do
  • else
  • for
  • function
  • if
  • in
  • instanceof
  • new
  • on
  • return
  • super
  • var
  • void
  • with

Annexe B : Les mots déconseillés

:arrow: Certains mots font partie intégrante de la norme ECMA-262 (PDF : ~700Ko), norme dont Flash tend à se rapprocher. Pour une plus grande probabilité de compatibilité avec les versions à venir, il vous est déconseillé d'utiliser les termes suivants pour nommer des variables, classes ou méthodes :

  • abstract
  • boolean
  • byte
  • catch
  • char
  • class
  • const
  • debugger
  • catch
  • double
  • enum
  • export
  • extends
  • final
  • finally
  • float
  • goto
  • implements
  • int
  • interface
  • long
  • native
  • package
  • private
  • protected
  • public
  • short
  • static
  • synchronized
  • throws
  • transitient
  • try
  • volatile