[213 total ]
[VEGAS] A - Design Pattern d'inversion de contrôle - Généralités

IoC ? Injection de dépendance ? Conteneur Léger ... depuis quelques temps maintenant nous entendons parler de ce Design Pattern très puissant qui révolutionne la construction des applications orientées objets. Ce Design Pattern est ... [More] vraiment pour moi indispensable une fois bien maitrisé et permet d'aller loin, très loin !

Depuis plusieurs mois maintenant je me suis lancé dans la construction de ma propre implémentation de ce pattern en m'inspirant d'une part de ce qu'il se fait de mieux à l'heure actuelle dans ce domaine ( Spring, Parsley, etc..) mais surtout des problèmes et besoins que je peux rencontrer chaque jour dans mes productions. Je remercie d'ailleurs tous ceux qui ont pris le temps de me supporter et de prendre le temps de poser un oeil sur mon code et au final qui m'ont aidé à aller au bout d'une version 1 finale (ou presque.. je finiolle :D ) de VEGAS et ses extensions.

La version AS3 de VEGAS est composée de plusieurs extensions très solides et spécialisées. Mais AndromedAS avec ses outils d'IoC, etc. est devenu le noyau dure du framework pour créer avec facilité une RIA moderne et fonctionnelle aussi bien dans Flash, Flex, ou dans une application AIR.

Je commence donc ici par un premier chapitre de mon énorme article basé sur le Design Pattern IoC dans VEGAS qui va se découper en plusieurs parties ici sur ce blog et qui finira au bout du compte en livre opensource disponible très prochainement sur les pages officielles du projet et aussi sur Google Documents. J'ai encore pas mal de fonctionnalités à documenter car la bibliothèque IoC s'est énormément étoffée ces derniers mois (pour la bonne cause).

J'espère que cette introduction, vous permettra de vous faire une idée clair et précise des bases du pattern IoC mais surtout de l'orientation que j'ai pu prendre pour l'implémenter. Cet article reste ouvert à tous commentaires et s'accompagnera très bientôt d'une seconde partie qui précisera le DOM très complet des définitions d'objets utilisée par le conteneur léger IoC pour créer des objets dans une application.

Comme toujours, je suis toute personne pouvant m'aider à accélérer le processus de traduction de cet article en anglais.

Pour tout ceux qui sont pressés de savoir la suite, vous pouvez me contacter pour que je vous donne en attendant de la communiquer ouvertement sur ce blog, l'url des autres parties (en construction) de ce tutoriel assez imposant (80 pages pour le moment sur Google Documents)

A noter que vous trouverez les sources des exemples ci-dessous dans le repository SVN du projet :

http://svn1.cvsdude.com/osflash/vegas/AS3/trunk/bin/test/andromeda/ioc/
http://svn1.cvsdude.com/osflash/vegas/AS3/trunk/bin/test/asgard/net/ (3 exemples d'utilisation de la classe ECMAObjectLoader)

Pour ceux qui débarquent dans l'aventure, et qui ne savent pas encore ce que représente VEGAS, comment l'installer etc.. Je vous propose d'aller faire un tour rapide sur le Google Code du projet :

http://code.google.com/p/vegas/
http://code.google.com/p/vegas/wiki/InstallVEGASwithSVN

Pour toutes vos questions sur VEGAS et ses extensions n'hésitez pas à vous inscrire sur le Google Groups :

VEGASoS : http://groups.google.com/group/vegasos

Enfin pour suivre le projet au mieux je peux vous proposer de consulter la page officielle de VEGAS sur Ohloh :

http://www.ohloh.net/projects/vegas

Il est temps maintenant de démarrer cet article sur le Design Pattern d'IoC et je vous promets la suite de cet article d'ici peu de temps

1 - Définitions

1-1 - Injection de dépendance (Dependency Injection)

Le design pattern "Dependency injection (DI)" se réfère à la pratique de l'injection de code dans une application. Les classes ne sont pas responsables dans l'instanciation des autres classes dont elles ont besoin. Les objets sont "injectés" au moment de la construction ou de l'initialisation de l'instance qui les utilise.

Ce modèle de conception consiste en plusieurs classes ou objets qui collaborent les uns avec les autres. Nous pouvons aussi parler de la notion de dépendance entre les objets.

Il existe plusieurs stratégies pour appliquer ce modèle de conception. Voyons donc de plus prêt les formes d'injection de dépendance existantes en général dans un code orienté objet.

Hard-coding dependencies :

La classe crée en interne ses propres dépendances. Cette technique est la plus simple pour gérer une dépendance mais elle reste la moins souple de toutes.

Exemple :

package
{
public class Writer
{
 
public function Writer()
{
}
 
public function write( message:String ):void
{
( new Pen() ).write( message ) ;
}
}
}

Dans l'exemple précédent la classe Writer utilise dans sa méthode write(message:String) un objet de type Pen. Cette méthode est donc dépendante d'un objet de type Pen pour fonctionner.

Le code est en effet très simple mais le fait de créer directement l'objet de type Pen dans la méthode limite très vite le champ d'action de la classe et toutes évolutions de celle-ci par la suite. En cas de changement il faudra modifier directement le code dans la classe ce qui n'est pas forcément très pratique.

Looking up dependencies :

Cette stratégie nécessite un contexte. La classe appelle un objet et utilise ses méthodes et attributs selon ses besoins. Le contexte est un objet externe qui contient donc toutes les dépendances.

Voici un petit exemple pour illustrer cette implémentation :

package
{
public class Writer
{
 
public function Writer()
{
}
 
public function write( message:String ):void
{
var context:Tools = new Tools() ;
context.getPen().write( message ) ;
}
}
}

Constructor injection :

L'injection par constructeur est une approche simple et classique. Cette approche du modèle utilise une classe qui possède une fonction constructeur avec des paramètres pour chaque dépendance que l'on souhaite définir.

Les dépendances sont transmises au moment de l'instanciation de la classe.

Exemple :

package
{
public class Writer
{
 
public function Writer( pen:Pen )
{
_pen = pen ;
}
 
private var _pen:Pen ;
 
public function write( message:String ):void
{
_pen.write( message ) ;
}
}
}

Dans cet exemple la fonction constructeur Writer( pen:Pen ) permet d'injecter au moment de la création d'une instance de type Writer la référence d'un objet de type Pen qui permettra à la méthode write() de fonctionner correctement.

Setter Injection :

Dans cette approche la classe possède des propriétés qui lui permettent de définir chacune de ses dépendances (attributs ou méthodes publiques).

Les dépendances sont définies après la création d'une instance de cette classe.

Exemple 1 : Avec une méthode ou un attribut publique de base.

package
{
public class Writer
{
 
public function Writer()
{
}
 
public var pen:Pen ;
 
public function setPen( p:Pen ):void
{
this.pen = p ;
}
 
public function write( message:String ):void
{
pen.write( message ) ;
}
}
}

Dans cet exemple la méthode 'setPen(p:Pen)' permet d'injecter dans les instance de la classe Writer tout objet de type Pen nécessaire au bon fonctionnement de la méthode write().

Exemple 2 : Avec une attribut virtuel (setter).

package
{
public class Writer
{
 
public function Writer()
{
}
 
private var _pen:Pen ;
 
public function set pen( p:Pen ):void
{
_pen = p ;
}
 
public function write( message:String ):void
{
_pen.write( message ) ;
}
}
}

Dans cet exemple la propriété virtuelle 'pen' permet d'injecter dans les instance de la classe Writer tout objet de type Pen nécessaire au bon fonctionnement de la méthode write().

Interface Injection :

Cette méthode s'utilise de la même manière que les principes de Setter Injection ou de Constructor injection mais en typant la dépendance (propriétés, arguments) avec une interface spécifique :

Exemple :

package
{
public class Writer
{
 
public function Writer()
{
 
}
 
private var _pen:IPen ;
 
public function get pen():IPen
{
return _pen ;
}
 
public function set pen( p:IPen ):void
{
_pen = p ;
}
 
public function write( message:String ):void
{
(_pen as IPen).write( message ) ;
}
}
}

1-2 - Inversion de contrôle

Le modèle de conception d'Inversion de contrôle, ou plus simplement appelé IoC (L'autre nom du modèle Dependency injection) définit un ensemble de techniques de programmation dans lesquels le flux de contrôle du système et de ses objets est inversé par rapport à une technique traditionnelle de création d'une application et de ses interactions.

Le modèle de conception d'inversion de contrôle se base sur le "Hollywood Principle" : "don't call us, we will call you" : « Ne nous appelez pas, c’est nous qui vous appellerons. »

Le principe est simple, il indique un framework d'application qui n'a pas besoin d'être appelé pour faire fonctionner l'application, c'est le framework lui même qui s'occupe de créer les interactions entre les objets (dans la limite du possible).

Ce modèle favorise la programmation en "couches". Cette programmation permet de séparer chaque élément d'une application et de travailler sur chacun de façon indépendante et ensuite de les lier les uns aux autres très simplement en ayant le moins de dépendances possible au départ. C'est donc au framework de lier chaque couche les unes avec les autres le moment venu en injectant toutes les dépendances dans l'objet qui en aura besoin.

1-3 - Conteneurs basés sur le modèle d'inversion de contrôle.

Un conteneur d'inversion de contrôle est une infrastructure d'application qui fournit de nombreux services indispensables pour créer et faire vivre une application. Voici les principaux services d'un conteneur IoC classique :

Lookup :

Le conteneur permet de stocker ou créer des références pour tous les objets de l'application. Le conteneur est donc également considéré comme une "fabrique" (factory) d'application.

Lifecycle management :

Le cycle de vie des objets dans une application est géré par le conteneur. Le conteneur est capable de créer des nouveaux objets, d'injecter automatiquement des valeurs prédéfinies dans les propriétés d'une instance au moment de sa création, d'invoquer des méthodes spécifiques au moment de l'initialisation et de la destruction des objets, etc.

Configuration :

Les objets de l'application peuvent être configurés simplement via des données externes sans avoir à recompiler l'application; Il est donc possible d'utiliser des données au format eden, JSON ou XML pour créer la fabrique et initialiser l'application.

Dependency resolution :

Le conteneur ne se contente pas de gérer et configurer les objets de l'application avec des types simples, il permet aussi de gérer les relations et dépendances que peuvent avoir les objets les uns avec les autres.

1-4 -Définition d'objet dans un conteneur IoC.

Une définition d'objet est un objet qui va permettre de créer un nouvel objet dans une application avec la fabrique IoC. Cette définition d'objet peut être considérée comme un "mode d'emploi" défini au préalable par les développeurs de l'application qui utilise ce conteneur.

Ce mode d'emploi contient entre autre :

un identifiant qui permet d'enregistrer la définition dans un conteneur léger et par la suite de créer des objets,
le type de l'objet que l'on souhaite créer,
les paramètres que l'on souhaite passer dans le constructeur,
les valeurs par défaut que l'on souhaite appliquer sur l'objet (propriétés et méthodes),
le nom de la méthode que l'on souhaite exécuter une fois l'objet créé pour l'initialiser le rendre actif dans l'application,
injecter les dépendances avec les autres objets définis eux aussi dans la fabrique,
etc.

La définition d'objet peut donner d'autres informations sur la stratégie utilisée par la fabrique pour créer l'objet ou sur la nature de cet objet dans le cycle de vie de l'application (singleton, prototype..).

Si l'on devait réduire à sa plus simple expression la définition d'un conteneur IoC, nous pourrions dire qu'il se compose d'une collection de définitions d'objets qui permettent de structurer efficacement et de façon solide une application tout au long de son cycle de vie.

2 - Configuration d'un conteneur IoC.

Pour créer et surtout remplir convenablement un conteneur IoC, il est possible d'utiliser tout simplement du code classique, mais sur des applications de moyenne ou grande taille cette technique peut s'avérer rapidement très fastidieuse et peu souple. Surtout qu'à chaque ajout dans le conteneur il faudra la plupart du temps relancer la compilation du programme.

Le modèle de conception d'inversion de contrôle à pour but de proposer des outils simples et d'optimiser efficacement le flux de production d'une application.

Il devient donc rapidement indispensable de compléter celui-ci par une gestion dynamique de son contenu. Dans la plupart des frameworks utilisant un design pattern IoC nous retrouvons un déploiement des applications au runtime (après compilation) via des fichiers de configuration externes souvent au format XML (exemple avec le framework Spring.NET ou Spring JAVA).

L'utilisation d'un ou plusieurs fichiers externes de configuration pour gérer une application reste la solution la plus souple. En intervenant après le lancement de l'application, il est possible de modifier le contenu du conteneur sans avoir à recompiler à chaque fois l'application. Nous parlerons alors d'un "conteneur léger".

De plus, il est possible avec un moteur de configuration avancé de faire évoluer l'application dynamiquement en fournissant au conteneur uniquement les définitions d'objets nécessaires et en alimentant régulièrement le conteneur avec de nouvelles définitions d'objets au fil du temps selon les besoins.

2-1 - Fichier de configuration basé sur le format de donnée "eden".

Le modèle de conception IoC AndromedAS propose une fabrique IoC basée sur des fichiers de configuration au format eden : ECMAScript Data Exchange Notation.

eden est à la base un outil de sérialisation/désérialisation d'objets via des données au format texte et sur une notation basée sur la norme ECMAScript écrit par Zwetan Kjukov. Cette notation permet l'échange de données entre un client et un serveur mais aussi la mise en place de configurations simples ou complexes dans nos applications.

Une des grandes forces du format eden est de proposer une structure de données basée sur un format texte très simple à utiliser et que l'on peut copier/coller très simplement pour l'utiliser directement dans un code ActionScript ou Javascript sans avoir à traiter la chaine de caractère récupérée dans un fichier de texte externe par exemple.

Il peut s'avérer facile de comparer après un bref coup d'œil le format eden avec le format JSON mais ce dernier reste vraiment pauvre par rapport à toutes les fonctionnalités que propose eden.

Le format eden accepte de nombreux types :

Array

//eden
 
a = [ 1, 2, 3 ] ;
b = new Array() ;

Boolean

//eden
 
b = true;
c = false;
d = new Boolean(); // d = false

Date

//eden
 
d1 = new Date();
d2 = new Date( 1974, 2, 30 );

Null

//eden
 
x1 =null ;// x1 = null
x2 ; //x2 = undefined

Number

//eden
 
n1 = 123;
n2 = 1.23;
n3 = 1e5;
n4 = 0xff;
n5 = -123;
n6 = -n2; // n6 = -1.23
n7 = new Number( 100 ); // n7 = 100

Object

//eden
 
o1 = { a:1, b:2, c:3 };
o2 = new Object(5); // o2 = 5
o3 = new Object();

String

//eden
 
s1 = "hello world";
s2 = "unicode supported ♠♣♥♦";
s3 = new String( "hello world" ); // s3 = "hello world"

Types customs définis par l'utilisateur

//eden
 
v1 = new Version( 1, 2, 3, 4 );
v2 = new system.Version( 1, 2, 3, 4 );

Il permet également les fonctionnalités suivantes :

Fonctionne avec n'importe quel client ou serveur basé sur la norme ECMA-262 (Javascript, ActionScript, JScript, Flash Media Server__, etc.)
Fonctionne avec la plupart des serveurs de données existant qui manipulent les chaînes de caractères.
Appel de fonctions et de méthodes sur les objets.
Gestion des données sécurisées avec isolation des scopes des objets et "white list" d'autorisation pour les types d'objets customs (types d'objets créés par le développeur dans son application).
Possibilité de définir plusieurs valeurs dans une même chaine de caractère.
Supporte les commentaires sur une ligne avec le séparateur // ou les commentaires multilignes avec les séparateurs /* */.

Il serait possible de créer des fichiers de configuration au format XML peut être plus classiques et plus "standard" si l'on compare les différents frameworks IoC actuels. Maintenant je trouve personnellement trop "verbeux" et très limitée la notation XML qui propose des fichiers de configurations complexes et qui entraine un énorme travail de parsing au niveau de l'application pour reproduire une partie infime des possibilités du parseur eden. La grande force de eden est de conserver dans le document de configuration, le type de tous les objets sans avoir à définir à chaque fois celui-ci.

Voyons pas exemple rapidement la différence entre une même définition d'objet définie par un schéma XML et un objet au format eden.

Prenons une classe simple pour illustrer notre exemple une classe test.User très simple écrite en ActionScript 3 :

package test
{
 
/**
* The User class.
*/
public class User
{
 
/**
* Creates a new User instance.
*/
public function User( pseudo:String = null, name:String = null , address:Address = null )
{
this.pseudo = pseudo ;
this.name = name ;
this.address = address ;
}
 
/**
* The Address reference of this object.
*/
public var address:Address ;
 
/**
* The age of the user.
*/
public var age:Number ;
 
/**
* The city of the user.
*/
public var city:String ;
 
/**
* The name of the User.
*/
public var name:String ;
 
/**
* Initialize the User.
*/
public function initialize():void
{
trace( this " initialize.") ;
}
 
}
 
}

Voyons tout d'abord un exemple de définition d'objet basé sur un schéma XML classique proche du DOM Spring.NET :

<objects>
 
<object name="address" type="test.Address" >
<constructor-arg index="0" value="34 xxx street"/>
</object>
 
<object
name = "user"
type = "myPackage.User"
init-method = "initialize"
>
 
<constructor-arg index="0" value = "ekameleon"/>
<constructor-arg index="1" value = "ALCARAZ" />
<constructor-arg index="2" ref = "address" />
 
<property name="age" value="31" />
<property name="city" value="marseille" />
 
</object>
 
</objects>

Voyons le même exemple (ou presque) avec une notation eden :

objects =
[
 
{
id : "user" ,
type : "myPackage.User" ,
init : "initialize" ,
arguments :
[
{ value : "ekameleon"} ,
{ value : "ALCARAZ"} ,
{ value : new test.Address("34 xxx street") }
]
,
properties :
[
{ name : "age" , value : 31 } ,
{ name : "city" , value : "marseille" }
]
}
 
] ;

A noter qu'il est possible d'utiliser des objets directement dans le code de vos applications pour alimenter le conteneur IoC sans passer par un fichier de configuration au format texte. Il est également possible de créer cet objet via un service web PHP, JAVA, Pyhon et de le renvoyer vers le client via le protocole texte classique (eden ou JSON) ou le protocol AMF (Action Message Format - Flash Remoting).

2-2 - Autres alternatives avec JSON ?

Même si je vous conseille vivement d'utiliser le format eden pour créer vos fichiers de configuration, si vous désirez absolument utiliser un format JSON pour alimenter le conteneur IoC, dans VEGAS, j'ai implémenté une adaptation de la classe JSON officielle. La classe vegas.string.JSON offre en plus des fonctionnalités classiques, la possibilité d'utiliser des nombres hexadécimaux (0xFF...) et surtout elle permet de désérialiser sans problème des expressions contenant des objets génériques définis avec ou sans double-quote ou même avec des simple-quotes :

import vegas.string.JSON ;
 
JSON.deserialize( { "prop" : 0xFF0000 } ) ;
JSON.deserialize( { 'prop' : 0x00FF00 } ) ;
JSON.deserialize( { prop : 0x0000FF } ) ;

Vous pouvez consulter pour plus d'information la documentation de la classe vegas.string.JSON dans la référence AS3 de VEGAS et ses extensions.

2-3 - Structure et DOM général des fichiers de configuration basés sur le format de donnée "eden".

Voici la structure principale objet d'une configuration pour une fabrique IoC dans le framework AndromedAS (extension de VEGAS) :

{
 
configuration : // only in the first configuration file
{
// ..
}
,
imports :
[
//..
]
,
objects :
[
//..
]
}

Il est aussi possible d'écrire plus simplement la structure précédente avec une chaine de caractère au format eden avec la structure suivante :

configuration =
{
// only in the first file
} ;
 
imports =
[
//..
] ;
 
objects =
[
//..
] ;

L'attribut "objects" est le plus important car il représente la collection (Array) de toutes les définitions d'objets que l'on souhaite insérer dans le conteneur IoC.

Les attributs "configuration" et "imports" sont facultatifs et sont utilisés via une classe spéciale mais non obligatoire pour utiliser les fonctionnalités de base du conteneur. L'attribut "configuration" sera utilisé uniquement dans le fichier principal de configuration utilisé pour lancer l'initialisation de la fabrique dans l'application.

Pour en savoir plus sur ces attributs, vous pourrez consulter le chapitre C - Les attributs facultatifs "configuration" et "imports" dans les fichiers de configuration d'une fabrique IoC. (Ce chapitre est en cours d'écriture et il arrivera très vite sur ce blog ;))

3 - La classe andromeda.ioc.factory.ECMAObjectFactory

3-1 - Description

La classe ECMAObjectFactory est la classe principale du moteur d'injection de dépendance définie dans le package andromeda.ioc.*.

Elle définit, à elle seule, le conteneur et la fabrique IOC qui sont utilisés par le framework pour gérer le contenu d'une application avec le Design Pattern d'Inversion de Contrôle.

Cette classe possède un héritage riche, avec plusieurs niveaux d'héritage, qui lui procure plusieurs fonctionnalités importantes :

ECMAObjectFactory ( Classe principale du framework IoC basée sur des définitions d'objet au format ECMAScript )
ObjectFactory :
ObjectDefinitionContainer
Action
SimpleAction
CoreEventDispatcher
CoreObject
Object

Cette classe implémente les interfaces :

IObjectDefinitionContainer : Définit l'ensemble des méthodes permettant de gérer les définitions d'objets dans le conteneur IoC (voir classe ObjectDefinitionContainer).
IObjectFactory : Définit l'ensemble des méthodes qui permettent la gestion et récupération des objets contenus dans la fabrique IoC (voir classe ObjectFactory).
IFactory : Définit la méthode create() qui permet de générer un ensemble de définition d'objets nécessaires pour une application donnée (voir classe ECMAObjectFactory).
IAction : Définit une notion de "process" sur la fabrique. La classe notifie un évènement au début et à la fin de l'initialisation complète du conteneur.
IRunnable : Définit que le conteneur peut être initialisé en exécutant simplement l'objet comme une commande avec la méthode run().

3-2 - Exemple d'utilisation en ActionScript ( "hello world" ).

Je vais illustrer l'utilisation de la fabrique IoC "ECMAObjectFactory" avec une initialisation simple basée sur simple objet de type Array qui contient un ensemble d'objets génériques. Ces objets génériques permettent l'initialisation et l'injection de plusieurs définitions d'objets et la création automatique de certains objets "singleton" pendant l'initialisation du conteneur léger avec certaines définitions d'objets.

L'exemple qui va suivre peut être testé aussi bien dans Flash CS3 que dans une application Flex ou même une application basée sur la technologie AIR. Cet exemple se base sur un bout de code saisi dans un calque sur la scène principale d'une animation dans Flash mais il est très simple d'adapter ce bout de code pour l'utiliser dans un projet AS3 basé sur une classe "main" d'application.

import andromeda.ioc.factory.ECMAObjectFactory ;
 
var objects:Array =
[
{
id : "my_format" ,
type : "flash.text.TextFormat" ,
arguments : [ { value:"arial" } , { value:24 } , { value:0xFEF292 } , { value:true } ]
}
,
{
id : "my_field" ,
type : "flash.text.TextField" ,
properties :
[
{ name : "autoSize" , value : "left" } ,
{ name : "defaultTextFormat" , ref : "my_format" } ,
{ name : "text" , value : "HELLO WORLD" } ,
{ name : "x" , value : 10 } ,
{ name : "y" , value : 10 }
]
}
,
{
id : "root" ,
type : "flash.display.MovieClip" ,
factoryReference : "#root" ,
singleton : true ,
methods :
[
{ name : "addChild" , arguments : [ { ref:"my_field" } ]}
]
}
] ;
 
 
var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
factory.config.root = this ;
 
factory.create( objects ) ;

L'objet "objects" de type Array de configuration ci-dessus permet de définir 3 définitions d'objet :

"my_format" : cette définition permet de créer des instances de types flash.text.TextFormat, cette définition injecte des dépendances via la fonction constructeur de la classe pour initialiser l'objet.
"my_field" : cette définition permet de créer des champs de texte dynamiques de type flash.text.TextField avec une initialisation de certains de ces attributs avec des injections de dépendance via l'attribut "properties".
"root" : cette définition permet de créer une référence singleton qui cible la scène principale de l'application définie dans la configuration de la fabrique IoC (voir code ci-dessous) et d'attacher dessus le champ de texte dynamique "my_field".

A noter que la définition d'objet "root" utilise une stratégie spéciale de la fabrique IoC qui lui permet de cibler dans une définition d'objet une référence de la scène principale définie dans le code ci-dessus avec la propriété root de l'objet de config de la fabrique :

factory.config.root = this ;

Remarque : La fabrique IoC possède plusieurs types de stratégies pour créer un objet dans l'application avec une définition d'objet. Nous en parlerons dans le prochain chapitre consacré aux définitions d'objets (Chapitre B, je suis en train de finaliser ce chapitre très long sur les définitions d'objets...).

L'exemple ci-dessus permet d'afficher très rapidement un champ de texte sur la scène d'une animation. Cet exemple nous montre bien l'utilisation d'une "programmation en couches" qui permet d'isoler chaque objet d'une application et de travailler tranquillement sur chacun d'eux sans se soucier de leurs dépendances les uns avec les autres. C'est le conteneur léger et le framework qui se chargent de créer les objets et de les mettre en relations les uns avec les autres.

Nous reprendrons dans le prochain chapitre cet exemple pour illustrer la technique utilisée pour chargée ce contexte de configuration avec un fichier au format texte externe (au format eden).

3-3 - Inspection des méthodes de la classe ObjectDefinitionContainer, base du conteneur IOC (interface IObjectDefinitionContainer)

Voici l'ensemble des méthodes et propriétés définies par l'interface IObjectDefinitionContainer implémentée par la classe EdenObjectFactory.

Voici un exemple simple d'utilisation des méthodes définies ci-dessus :

import flash.text.TextField ;
import flash.text.TextFormat ;
 
import andromeda.ioc.core.ObjectDefinition ;
import andromeda.ioc.core.ObjectDefinitionContainer ;
import andromeda.ioc.factory.ObjectFactory ;
 
var container:ObjectDefinitionContainer = new ObjectFactory();
 
var context:Object =
{
id : "my_field" ,
type : "flash.text.TextField" ,
properties :
[
{ name : "defaultTextFormat" , value : new TextFormat("verdana", 11) } ,
{ name : "selectable" , value : false } ,
{ name : "text" , value : "hello world" } ,
{ name : "textColor" , value : 0xF7F744 } ,
{ name : "x" , value : 100 } ,
{ name : "y" , value : 100 }
]
}
 
var definition:ObjectDefinition = ObjectDefinition.create( context ) ;
 
container.addObjectDefinition( definition );
 
trace( container.containsObjectDefinition( "my_field" ) ) ; // true
trace( container.getObjectDefinition( "my_field" ) ) ; // [ObjectDefinition]
trace( container.sizeObjectDefinition() ) ; // 1
 
var field:TextField = (container as ObjectFactory).getObject("my_field") as TextField ;
 
addChild(field) ;

Dans cet exemple nous initialisons une définition d'objet avec un objet générique simple qui permettra de générer avec la fabrique une instance de la classe TextField spécifique qu'il sera très simple ensuite d'attacher sur la scène. Cette technique simple permet d'injecter à tout moment des nouvelles définitions d'objets à la main dans la fabrique avec la méthode addObjectDefinition().

3-4 - Inspection des propriétés et méthodes de la classe ObjectFactory, base de la fabrique IOC (interface IObjectFactory)

Voici l'ensemble des méthodes et propriétés définies par l'interface IObjectFactory implémentée par la classe EdenObjectFactory (hérite de la classe ObjectFactory). La classe ObjectFactory reste de plus bas niveau et permet à ceux qui le désirent de garder le moteur IoC de AndromedAS en utilisant un parseur XML ou autre pour remplir la fabrique.

3-5 - Inspection des propriétés et méthodes de la classe ECMAObjectFactory.

Voici l'ensemble des méthodes statiques définies dans la classe EdenObjectFactory.

Cette classe contient le moteur d'initialisation de la fabrique. Le conteneur interprète une collection de type Array contenant des objets génériques simples (format ECMAScript) et peut ainsi créer ses propres définitions d'objets de type ObjectDefinition.

La classe ECMAObjectFactory contient aussi un ensemble de méthodes statiques (voir tableau ci-dessus) qui permettent de créer plusieurs instances globales dans une application (voir chapitre A.3-5).

3-6 - Plusieurs fabriques IoC dans vos application.

La classe EdenObjectFactory peut être instanciée tout simplement avec le mot clé new mais si nous regardons de plus prêt nous pouvons oberver dans sa signature qu'elle implémente l'interface vegas.core.Identifiable. Cette interface définie l'existance d'un attribut "id" qu'il est possible de définir sur toutes les instances de la classe.

De façon générale, ce petit attribut peut être pratique mais il trouve tout son intérêt si l'on utilise la fonctionnalité "multi-singleton" de la classe. Cette notion permet de définir plusieurs références globales uniques dans l'application de type ECMAObjectFactory. On peut ainsi utiliser dans une même application plusieurs modules définis par des fabriques IoC totalement indépendantes.

La classe EdenObjectFactory contient donc une méthode statique getInstance( id:String=null ) qui permet de créer et renvoyer des références uniques de la classe en fonction d'un identifiant spécifique et lui aussi unique. La classe contient d'autres méthodes statiques permettant de supprimer complètement un singleton de la classe en mémoire mais aussi de vérifier si pour un identifiant donné, un singleton de la classe existe.

Exemple :

import andromeda.ioc.factory.ECMAObjectFactory;
 
var factory:ECMAObjectFactory;
 
factory = ECMAObjectFactory.getInstance() ;
 
trace( "ECMAObjectFactory.getInstance() : " factory " with the default id : " factory.id ) ;
 
factory = ECMAObjectFactory.getInstance("factory_one") ;
 
trace( "ECMAObjectFactory.getInstance('factory_one') : " factory " with the default id : " factory.id ) ;
 
trace( "ECMAObjectFactory.containsInstance('factory_one') : " ECMAObjectFactory.containsInstance( "factory_one" ) ) ;
 
trace( "ECMAObjectFactory.removeInstance('factory_one') : " ECMAObjectFactory.removeInstance( "factory_one" ) ) ;
 
trace( "ECMAObjectFactory.containsInstance('factory_one') : " ECMAObjectFactory.containsInstance( "factory_one" ) ) ;

4 - La classe asgard.net.ECMAObjectLoader.

4-1 - Définition et exemple de base.

La classe ECMAObjectLoader est une classe un peu particulière car elle ne se trouve pas directement dans l'extension AndromedAS. En effet pour utiliser cette classe il faut la cibler dans le package asgard.net.* de l'extension ASGard de VEGAS.

Cette classe est utilisée pour charger un ou plusieurs fichiers externes qui permettront d'initialiser la fabrique IoC.

Exemple :

import asgard.net.ECMAObjectLoader ;
 
var loader:ECMAObjectLoader = new ECMAObjectLoader( "application.eden" , "context/" ) ;
 
loader.run() ;

Toute instance de la classe ECMAObjectLoader contient par défaut une référence vers la référence singleton définie par défaut de la classe ECMAObjectFactory mais il est possible de modifier cette référence avec tout autre objet du même type.

La classe ECMAObjectLoader utilise également par défaut un chargeur de données externe basé sur un parsing eden avec la classe asgard.net.EdenLoader. Vous pouvez en cas de besoin modifier ce chargeur pas un autre. Par exemple dans le cas d'un chargement de données via un fichier texte au format JSON vous pouvez utiliser la classe asgard.net.JSONLoader.

Vous pouvez créer vos propres chargeurs/parseurs/désérialisations de données avec un format XML ou autre en créant votre propre classe de chargement qui héritera de la classe asgard.net.ParserLoader.

La classe ECMAObjectLoader est un objet basé sur le modèle de commandes et d'actions de AndromedAS, elle implémente l'interface andromeda.process.IAction. Une fois les fichiers de configurations chargées la classe ECMAObjectLoader remplit le conteneur d'Inversion de Contrôle.

4-2 - Exemple d'utilisation : "hello world".

Illustrons l'utilisation de la fabrique IoC "ECMAObjectFactory" avec son chargeur de configurations externes "ECMAObjectLoader" avec un exemple basique, en créant dynamiquement un champ de texte dans une animation au format SWF.

Tout d'abord, nous allons créer un fichier au format texte (UTF8) externe de configuration avec la chaîne de caractères (format eden) suivante :

objects =
[
{
id : "my_format" ,
type : "flash.text.TextFormat" ,
arguments : [ { value:"arial" } , { value:24 } , { value:0xFEF292 } , { value:true } ]
}
,
{
id : "my_field" ,
type : "flash.text.TextField" ,
properties :
[
{ name : "autoSize" , value : "left" } ,
{ name : "defaultTextFormat" , ref : "my_format" } ,
{ name : "text" , value : "HELLO WORLD" } ,
{ name : "x" , value : 10 } ,
{ name : "y" , value : 10 }
]
}
,
{
id : "root" ,
type : "flash.display.MovieClip" ,
factoryReference : "#root" ,
singleton : true ,
methods :
[
{ name : "addChild" , arguments : [ { ref:"my_field" } ]}
]
}
] ;

Le fichier de configuration externe permet de définir 3 définitions d'objet :

"my_format" : cette définition permet de créer des instances de types flash.text.TextFormat, cette définition injecte des dépendances via la fonction constructeur de la classe pour initialiser l'objet.
"my_field" : cette définition permet de créer des champs de texte dynamiques de type flash.text.TextField avec une initialisation de certains de ces attributs avec des injections de dépendance via l'attribut "properties".
"root" : cette définition permet de créer une référence singleton qui cible la scène principale de l'application définie dans la configuration de la fabrique IoC (voir code ci-dessous) et d'attacher dessus le champ de texte dynamique "my_field".

Remarque : le nom d'extension ".eden" n'est pas obligatoire, il suffit que le fichier externe contienne juste du texte au format eden pour que le chargeur fasse correctement son travail. Il est donc possible d'utiliser des fichiers portant l'extension .txt par exemple, même si il est tout de même plus simple d'utiliser une extension différente pour distinguer correctement vos fichiers de configuration au format eden par rapport à d'autres.

Il ne reste plus qu'à charger ce fichier de configuration et de remplir un conteneur IoC avec la classe ECMAObjectLoader :

import andromeda.events.ActionEvent;
 
import asgard.net.ECMAObjectLoader ;
 
var debug:Function = function( e:Event ):void
{
trace( e ) ;
}
 
var loader:ECMAObjectLoader = new ECMAObjectLoader( "hello_world.eden" , "context/" ) ;
 
loader.root = this ; // to use the "#root" expression in the 'ref' attribute.
 
loader.addEventListener( ActionEvent.START , debug ) ;
loader.addEventListener( ActionEvent.FINISH , debug ) ;
 
loader.run() ;

L'exemple ci-dessus illustre :

L'utilisation de plusieurs définitions d'objets chargées dans un fichier externe.
La notion de dépendance entre une définition d'objet et une autre
La notion de dépendance avec une référence "magique" (#root) qui permet de cibler la référence d'un objet spécial défini dans l'application.
La méthode pour créer un objet visuel (ici un champ de texte) directement avec la fabrique sans appeler sa définition dans le code ActionScript.

Cet exemple très basique dévoile quelques fonctionnalités disponibles dans la moteur IoC de AndromedAS. Nous chargeons un fichier texte encodé en UTF8 "hello_world.eden" défini dans un répertoire "context/" situé au même niveau que le swf de l'application de test.

Le constructeur de la classe ECMAObjectLoader permet de définir le nom complet du fichier de configuration que l'on souhaite charger mais aussi le répertoire principal dans lequel il se trouve. Si aucun paramètre n'est défini dans la fonction constructeur de la classe alors le chargeur essaie de charger un fichier "application.eden" situé au même niveau que le swf de l'application.

Ce fichier texte est ensuite parsé par le moteur de désérialization eden puis injecté automatiquement dans le fabrique IoC principale de l'application définie dans la classe ECMAObjectLoader par défaut :

import andromeda.ioc.factory.ECMAObjectFactory ;
import andromeda.ioc.factory.IObjectFactory ;
 
import asgard.net.ECMAObjectLoader ;
 
var loader:ECMAObjectLoader = new ECMAObjectLoader() ;
 
var factory:IObjectFactory = loader.factory ;
 
trace( factory == ECMAObjectFactory.getInstance() ) ; // true

Il est bien entendu possible de modifier cette référence par une autre de type IObjectFactory à tout moment dans l'application mais il est tout de même préférable de tenter ce genre d'opérations en maitrisant parfaitement le moteur IoC de AndromedAS.

La classe ECMAObjectLoader possède un modèle évènementiel simple basée sur le moteur d'Action et process de AndromedAS qui lui permet de notifier l'utilisateur du début et de la fin de l'initialisation du conteneur léger en passant par un "helper" de type ECMAObjectLoader.

import andromeda.events.ActionEvent;
 
import asgard.net.ECMAObjectLoader ;
 
var debug:Function = function( e:Event ):void
{
trace( e ) ;
}
 
var loader:ECMAObjectLoader = new ECMAObjectLoader( "hello_world.eden" , "context/" ) ;
 
loader.addEventListener( ActionEvent.START , debug ) ;
loader.addEventListener( ActionEvent.FINISH , debug ) ;

Il existe une multitude de fonctionnalités disponibles dans les définitions d'objets des conteneurs léger définis dans AndromedAS. Nous allons détailler ces fonctions, stratégies et options dans la prochaine grosse partie de cet article sur le Design Pattern d'injection de contrôle implémenté dans l'extension AndromedAS de VEGAS. [Less]

Problème avec SWC AS3 de VEGAS

Hello :)
Petit message d'infos pour vous parler d'un petit soucis avec le swc
disponible pour le moment dans le répertoire libs/ de la version AS3
de VEGAS. J'utilisais depuis quelques jours Flex Builder 3 pour
générer ce SWC ... [More] mais je n'avais pas vu qu'en fait les SWC dans Flex
Builder 3 sont en version 1.2 (fallait le savoir ^_^) [Less]

VEGAS et AST'r sur OHLHO

Hello :)
Petit message rapide pour vous annoncer que j'ai inscris VEGAS et
AST'r sur Ohloh :
[link]
[link]
Ce portail communautaire est un incontournable pour les projets
opensource avec des outils d'analyse sur les révisions, commits, etc.

[AS3] Nombre maximum de récursions en AS3 dans Flash CS3

Voici un petit point que je n'avais pas encore eu le temps de regarder depuis que j'utilise Flash CS3 à propos du "nombre de récursions" dans Flash CS3 lorsque nous publions une animation avec les paramètres Flash Player 9 et ActionScript ... [More] 3.

Ce n'est pas forcément un mystère pour tout le monde mais je me rends compte que malgré tout ce point n'est jamais vraiment traité.

En ActionScript il est impossible d'utiliser un algorithme basé sur une ou des fonctions récursives indéfiniment.

Pour rappel, une fonction récursive est une fonction qui s'appelle elle même.

Par exemple si l'on veut calculer très simplement le produit factoriel d'un nombre entier (multiplication des valeurs n-1 du nombre) nous pouvons écrire la fonction suivante :

var factoriel:Function = function( n:uint ):uint
{
if ( n <= 1 )
{
return 1 ;
}
else
{
return n * factoriel(n-1) ;
}
}
 
trace( factoriel(10) ) ; // 3628800

La fonction précédente n'est pas la plus optimale (pas le but de cet article) mais elle illustre bien que la fonction "factoriel" s'appelle elle même plusieurs fois avant d'obtenir le bon résultat.

Il faut donc faire attention en utilisant une fonction récursive car si elle est mal définie il est possible "en principe" d'obtenir une boucle infinie qui pourrait faire planter l'application.

C'est pour cela que l'ActionScript (ou plus généralement les langages basés sur l'ECMAScript comme le Javascript) limitent leur nombre de récursions.

En AS2 et AS1 nous avions l'habitude de cette limite, avec un nombre maximum de récursion basé sur le nombre 255. Je vais reprendre l'exemple précédent mais en AS2 pour illustrer cette limite :

var factoriel:Function = function( n:Number ):Number
{
if ( n <= 1 )
{
return 1 ;
}
else
{
return n * factoriel(n-1) ;
}
}
 
trace( factoriel(10) ) ;
trace( factoriel(255) ) ;
trace( factoriel(256) ) ;

Nous obtenons dans le panneau de sortie de Flash :

3628800
Infinity
256 levels of recursion were exceeded in one action list.
This is probably an infinite loop.
Further execution of actions has been disabled in this movie.

La limite de récursion en AS1/AS2 est donc très vite atteinte. L'idée à la base était que l'ActionScript (comme le Javascript) étaient des langages qui ne nécessités pas une aussi forte sollicitation algorithmique. De façon générale les langages basés sur les normes ECMAScript préfèrent l'utilisation d'algorithmes itératifs que récursifs.

A noter qu'il est possible de changer le nombre de récursions ainsi que le temps maximum d'exécution d'un script en AS1 ou AS2 avec un utilitaire externe en ligne de commande : SWF ScriptLimits Injector. Cet utilitaire fonctionne avec un swf au format FP7 et (je ne l'ai pas testé avec un code en AS3 ?)

Maintenant que nous avons vu qu'il existe une limite dans l'AVM1 (virtual machine du FlashPlayer en AS1 et l'AS2), il est temps de voir ce qu'il se passe dans Flash CS3 en AS3 avec un bout de code super simple

var cpt:Number = 0 ;
 
var max:Number = 3035 ;
 
var test:Function = function()
{
cpt ;
if ( cpt < max )
{
test() ;
}
}
 
test() ;
 
trace(cpt) ; // 3035

Avec le code ci-dessus nous obtenons un résultat évidant, avec une valeur 3035 dans le panneau de sortie. Première constatation, nous pouvons dépasser dans Flash CS3 la limite de 255 récursions et donc il est possible d'aller bien au delà !

Voyons maintenant le même script mais avec une valeur maximale de 3036 :

var cpt:Number = 0 ;
 
var max:Number = 3036;
 
var test:Function = function()
{
cpt ;
if ( cpt < max )
{
test() ;
}
}
 
test() ;
 
trace(cpt) ; // Error...

Nous obtenons une erreur dans le panneau de sortie de Flash :

Error: Error #1023: Il s'est produit un débordement de pile.
at MethodInfo-1()
at MethodInfo-1()
at MethodInfo-1()
at MethodInfo-1()
at MethodInfo-1()
at MethodInfo-1()
at MethodInfo-1()
....

Par défaut dans Flash CS3 nous avons donc une limite du nombre de récursions avec 3035 récursions possibles.

A noter que dans le compilateur de Flex (mxmlc.exe) il est possible de changer via une commande le nombre limite de récursion du swf compilé :

-default-script-limits <max-recursion-depth> <max-execution-time>

Pour plus d'informations à ce sujet vous pouvez consulter la documentation de Flex ou le très bon article sur senocular.com : Beginners Guide to Getting Started with AS3 (Without Learning Flex)

Je n'ai pas encore trouvé comment... mais je me demande si il est possible de faire de même avec Flash CS3 ?

Pour toutes vos questions sur cet article vous pouvez utiliser les commentaires de ce blog mais je vous propose d'aller discuter sur le Google Groups de mon blog. [Less]

Questions sur ScrollContainerDisplay

Bonjour ekameleon, puisque nouveau ici, je tiens d'abord à te
féliciter pour ton FW, je suis dedans depuis ce WE et je m'aperçois
qu'il vaut le détour.
Bon, quand même mes petites questions sur le ScrollContainerDisplay
qui ... [More] pourrait m'être très utile.
J'essaye de le mettre en place avec un mask, genre la zone où seront [Less]

[AS3] MovieClip.play() inactif au lancement d'une application.

J'ai beaucoup de travail en ce moment pour finaliser les outils permettant d'utiliser le design pattern d'Inversion de Contrôle avec VEGAS (mon framework opensource) et je suis en train d'écrire un gros article (50aines de pages !) sur le sujet qui ... [More] arrivera d'ici peu sur ce blog et via une publication opensource basée sur des documents au format Google Documents

En attendant, voici un petit article très simple pour vous parler d'un mini "problème" découvert par des amis à moi il y a quelques semaines. Je devrais parler d'un "changement de comportement" plutôt que d'un problème...

Voyons donc de plus prêt le comportement de la méthode MovieClip.play() en ActionScript 3.

Nature du problème

Pour tester et isoler le problème de la méthode MovieClip.play() en AS3, il suffit de suivre la méthodologie suivante :

1 - Ouvrir Flash CS3 et créer un nouveau document.

2 - Créer un MovieClip dans la bibliothèque du document ouvert et à l'intérieur créer une petite interpolation de forme ou de mouvement pour que le clip contienne une petite animation sur plusieurs images clés.

3 - Créer un calque de code dans le scénario (timeline) du clip et mettre un stop(); sur la première image clé du clip.

4 - Poser une occurrence du symbole sur la scène et lui donner un nom d'occurrence "mc" dans le panneau de propriété en ayant bien pris le temps de sélectionner le clip avant.

5 - Créer sur la scène principale un calque de code au dessus du calque qui contient l'occurrence et taper le code suivant :

mc.play() ;

Si nous essayons de compiler le swf (CTRL ENTER) avec une publication au format AS1 ou AS2 (voir paramètre dans panneau des paramètres de publication du document), pas de problème l'animation du clip se lance sans problème.

Si par contre nous essayons de compiler avec un format AS3 avec une publication pour le FlashPlayer 9 alors le clip ne lance pas l'animation est reste figé sur sa première image clé.

Cause du problème

Avec l'AS3, l'ActionScript a changé au niveau de la gestion des éléments visuels d'une animation. En effet maintenant les clips, les champs de textes, les éléments vidéos, etc. appartiennent tous au groupe des objets de type "flash.display.DisplayObject".

Tous ces objets peuvent être attachés sur la scène principale d'une animation ou dans d'autres éléments graphiques comme avant mais ils ne dépendent plus uniquement du clip "parent" dans lequel ils vont être attachés.

En effet, il faut regarder dans la documentation de l'ActionScript 3 au niveau de la classe flash.display.DisplayObjectContainer pour comprendre qu'il est maintenant possible de créer un clip (ou autre élément visuel) dans l'animation sans avoir obligatoirement à l'attacher dans un autre.

En AS3 les classes Loader, Sprite et MovieClip du package flash.display.* héritent de la classe DisplayObjectContainer et peuvent donc attacher toute sorte d'éléments graphique si ils héritent de la classe DisplayObject.

Voyons un petit exemple d'utilisation de cette fonctionnalité :

import flash.display.* ;
 
var container:Sprite = new Sprite() ;
var shape:Shape = new Shape() ;
 
shape.x = 25 ;
shape.y = 25 ;
 
shape.graphics.beginFill( 0xFF0000 ) ;
shape.graphics.drawRect( 0, 0, 50, 50 ) ;
 
addChild( container ) ;
 
container.addChild( shape ) ;

Dans l'exemple ci-dessus nous pouvons donc créer les instances de types Sprite et Shape avant de les attacher dans la scène principale plus loin dans le code.

Pour ce qui est maintenant d'un MovieClip attaché dans un DisplayObjectContainer, il suffit de faire un petit test rapide pour s'apercevoir d'un mode de fonctionnement un peu particulier par rapport à la version AS1 ou AS2 de la classe.

Je modifie le code défini plus haut pour illustrer le problème et j'ajoute un petit test avec une écoute des touches du clavier pour lancer l'animation :

import flash.events.KeyboardEvent ;
 
mc.play() ; // ne fonctionne pas
 
var keyDown:Function = function( e:KeyboardEvent ):void
{
mc.play() ; // fonctionne si nous appuyons sur une touche
}
 
stage.addEventListener( KeyboardEvent.KEY_DOWN , keyDown ) ;

En appuyant sur une touche après l'ouverture de l'animation et une fois que le clip est totalement attaché sur la scène, nous pouvons alors utiliser la méthode play() sans soucis.

Il faut donc un certain temps au player pour initialiser le MovieClip et que nous puissions utiliser la méthode, nous pouvons aussi utiliser la méthode setTimeout pour illustrer ce temps d'attente nécessaire avant d'utiliser la méthode play() :

mc.play() ; // ne fonctionne pas
 
var run:Function = function():void
{
mc.play() ; // fonctionne après un délais de 150ms par exemple
}
 
setTimeout( run , 150 ) ;

Nous pouvons aussi utiliser un petit hack en utilisant la propagation évènementielle de la classe Stage et en écoutant l'évènement de type Event.ACTIVATE :

import flash.events.Event ;
 
mc.play() ; // ne fonctionne pas
 
var activate:Function = function( e:Event ):void
{
trace("activate") ;
mc.play() ; // fonctionne
}
 
stage.addEventListener( Event.ACTIVATE , activate ) ;

Finalement nous pouvons dire que lorsque nous attachons un MovieClip ou tout DisplayObject dans un objet de type DisplayObjectContainer il faut absolument faire attention au temps d'initialisation de l'élément graphique dans son container avant de vouloir interagir dessus rapidement. Certaines propriétés et méthodes seront initialisées immédiatement alors que d'autres seront un peu plus capricieuses.

Solution

Finalement, il est impossible de lancer la méthode play() d'un MovieClip avant que celui-ci soit totalement attaché dans un container de l'application.

Personnellement pour remédier à ce petit problème j'utilise la fonction non documentée addFrameScript pour hacker le MovieClip et lui demander de forcer l'exécution de sa méthode play() au moment que je le désire, pour cela il suffit de taper le code suivant :

mc.addFrameScript( 0 , mc.play ) ;

De façon général il faut donc bien réfléchir avant de lancer un clip dans une animation et prendre en compte ce comportement un peu spécial de l'AS3.

Pour toutes vos questions sur cet article vous pouvez utiliser les commentaires de ce blog mais je vous propose d'aller discuter sur le Google Groups de mon blog. [Less]

Test unitaire de calista.hash.TEA

Salut,
J'ai fait les tests unitaires de tout le package calista.hash et une
seule fonction ne passe pas le test.
c' est la fonction TEA.decrypt()
Effectivement en faisant ça:
import calista.hash.TEA ;
var source:String = "hello world is secret" ;
var password:String = "calista" ;

[VEGAS] [AS3] AndromedAS - le Design Pattern Visitor.

Introduction

Voici la nouvelle version mise à jour de mon tutoriel sur le Design Pattern Visitor avec cette fois ci un exemple tout en AS3 optimisé et basé sur la toute dernière version de AndromedAS. Ce tutoriel est le premier d'une ... [More] série de plusieurs autres qui vont illustrer tout doucement toutes les possibilités offertes par l'extension AndromedAS de VEGAS.

Pour ceux qui voudraient relire l'ancienne version de ce tutoriel je vous propose d'aller faire un tour : sur la page de la version AS2 du pattern Visitor

Ce tutoriel remonte à décembre 2006, le temps passe vite ! Depuis de l'eau a coulée sous les ponts et les implémentations du pattern Visitor ont changé de package et se trouvent maintenant dans le package andromeda.util.visitor et plus dans le package vegas.util.* .

A - Généralité

1 - Définition

Le Design Pattern Visitor permet simplement d'ajouter sur une classe ou instance des fonctionnalités non prévues au départ sans surcharger la structure de base de l'objet et en gardant si possible une certaine flexibilité et souplesse pour ajouter ou enlever rapidement d'autres fonctionnalités sans alourdir à nouveau l'implémentation définie au départ.

Je vous conseille de lire en complément de mon explication les articles à ce sujet sur Wikipedia qui vous apporteront peut être quelques explications supplémentaires sur le sujet.

Pour faire simple, le Design Pattern Visitor s'articule autour d'une classe qui pourra être "visitée" par d'autres classes via une méthode accept() (le nom de la fonction peut varier selon les implémentations du pattern). Cette méthode "accept" prend en paramètre un objet de type "visiteur" et va lancer automatiquement la méthode visit() du "visiteur". Ainsi l'objet visiteur pourra contrôler convenablement l'objet visité en récupérant simplement la référence de cet objet.

2 - Implémentation dans AndromedAS

Dans AndromedAS j'ai implémenté 2 interfaces très simples pour structurer ce Design Pattern. Il est bien entendu possible de juste s'inspirer de ces 2 interfaces pour réaliser d'autres conceptualisations selon des besoins précis. Mais dans mes travaux quotidiens ces 2 interfaces sont suffisantes.

2-1 - L'interface andromeda.util.visitor.IVisitable des "visiteurs".

package andromeda.util.visitor
{
 
/**
* The basic IVisitable interface.
* @author eKameleon
*/
public interface IVisitable
{
 
/**
* Accept the IVisitor object
*/
function accept( visitor:IVisitor ):void ;
 
}
}

2-2 - L'interface andromeda.util.visitor.IVisitor des objets qui vont être "visités".

package andromeda.util.visitor
{
 
/**
* The basic IVisitor interface.
* To implements the Visitor pattern you can creates a concrete Visitor class who implements this interface.
* @author eKameleon
*/
public interface IVisitor
{
 
/**
* Visit the IVisitable object.
*/
function visit( o:IVisitable ):void ;
 
}
}

2-3 - La classe abstraite AbstractVisitable

Pour simplifier l'implémentation de ce pattern j'utilise parfois la classe abstraite AbstractVisitable qui permet rapidement d'implémenter ce Design Pattern. Il est bien entendu possible de juste s'inspirer de cette classe pour orienter différemment le Design Pattern selon vos besoins dans vos propres classes qui implémenteront simplement l'interface IVisitable.

package andromeda.util.visitor
{
 
import vegas.core.CoreObject;
 
/**
* The abstract representation of the IVisitable interface.
* To implements a Visitor pattern you must inspired your IVisitor classes with this interface.
* This Abstract class is a basical implementation of the Visitor pattern, you can inspirate your custom Visitor design pattern implementation with it easy representation.
* @author eKameleon
*/
public class AbstractVisitable extends CoreObject implements IVisitable
{
 
/**
* Abstract constructor to creates a concrete constructor when this constructor is extended.
*/
public function AbstractVisitable()
{
super();
}
 
/**
* Accept a IVisitor object.
* You can overrides this method in complexe Visitor pattern implementation.
*/
public function accept(visitor:IVisitor):void
{
visitor.visit(this) ;
}
 
}
}

Toute classe qui hérite de la classe AbstractVisitable va tout simplement récupérer en paramètre via sa méthode accept() des instances de type IVisitor et lancer automatiquement leur méthode visit(). Chaque classe de type IVisitor servira à implémenter une nouvelle fonctionnalité sur la classe IVisitable (notion de plugin).

B - Exemple d'utilisation du Design Pattern (version AS3)

1 - Description et architecture

Nous allons réaliser maintenant un petit exemple simple qui illustrera l'utilisation du Design Pattern Visitor avec la mise en place d'un conteneur graphique qui servira de support pour afficher une image bitmap chargée dynamiquement.

L'exercice est simple, nous allons créer un Sprite qui servira de conteneur pour afficher une image. Nous allons nous servir de la propriété graphics:Graphic de la classe Sprite pour dessiner un fond rectangulaire dans le conteneur (fond de l'image). J'ai choisi de me baser sur la taille de l'image contenue dans l'exemple pour créer un conteneur avec une taille de 260x260 pixels. L'image sera positionnée au centre de la scène principale de l'application.

Dans ce conteneur nous allons ensuite créer un Loader qui permettra à tout moment de charger une image bitmap ou un fichier .swf externe.

Vous pouvez retrouver à tout moment les sources de cet exemple dans les sources AS3 du framework dans le répertoire AS3/trunk/bin/tutorials/andromeda/visitor du repository subversion.

Remarque : Si vous n'avez pas encore installé les sources de VEGAS sur votre machine de travail, je vous conseille vivement de le faire avant de continuer la lecture de cet exemple.

Installation des sources de VEGAS avec un client SVN : http://code.google.com/p/vegas/wiki/InstallVEGASwithSVN

La structure de ce petit tutoriel reste simple et classique :

1-1 - Répertoire contenant le fichier .fla (pour Flash CS3)

visitor/bin/visitor.fla : répertoire contenant les sources AS3 du tutoriel.

1-2 - Répertoires contenant le fichier .swf et l'image bitmap externe :

visitor/deploy/visitor.swf : fichier de production de l'exemple.
visitor/deploy/library/picture1.jpg : image bitmap chargée par le fichier visitor.swf.

1-3 - Répertoire de base des sources du projet (classpath) :

visitor/src/ : répertoire contenant les sources AS3 du tutoriel.
visitor/src/Visitor.as : Classe principale de l'application.

Si vous tester dans Flash CS3 cet exemple il faut ajouter le répertoire src/ de ce petit projet dans les chemins des classes ActionScript :

Edition » Préférence » Paramètres d'ActionScript 3.0 » Ajouter le chemin dans la liste avec le bouton et ensuite en ciblant le répertoire sur votre disque dur.

1-4 - Répertoire contenant les classes qui permettent de gérer les vues de l'application

visitor/src/display/
visitor/src/display/PictureDisplay.as : classe principale de la vue de l'application (conteneur graphique d'une image bitmap).
visitor/src/display/UIList.as : énumération statique de toutes les vues de l'applications (identifiant unique pour chaque vue importante).

1-5 - Répertoire contenant les IVisitors de la classe PictureDisplay

visitor/src/visitors/ClearVisitor.as : Ce IVisitor vide l'affichage du conteneur (faire disparaitre l'image uniquement)
visitor/src/visitors/HideVisitor.as : Ce IVisitor cache complètement le conteneur.
visitor/src/visitors/LoaderVisitor.as : Ce IVisitor contient les instructions pour charger une image bitmap externe dans le conteneur.
visitor/src/visitors/ShowVisitor.as : Ce IVisitor affiche complètement le conteneur.

2 - Création de la classe PictureDisplay

Voyons tout d'abord le code source de cette classe :

package visitor.display
{
import flash.display.*;
import flash.events.Event;
 
import andromeda.util.visitor.IVisitable;
import andromeda.util.visitor.IVisitor;
 
/**
* The PictureDisplay class.
*/
public class PictureDisplay extends Sprite implements IVisitable
{
 
/**
* Creates a new PictureDisplay instance.
*/
public function PictureDisplay()
{
super() ;
 
loader = new Loader() ;
loader.contentLoaderInfo.addEventListener( Event.COMPLETE, complete ) ;
 
update() ;
}
 
/**
* The virtual height of the picture.
*/
public var h:uint = 260 ;
 
/**
* The loader of the picture display.
*/
public var loader:Loader ;
 
/**
* The virtual hwidth of the picture.
*/
public var w:uint = 260 ;
 
/**
* Accept a IVisitor object.
*/
public function accept( visitor:IVisitor ):void
{
visitor.visit(this) ;
}
 
/**
* Invoked when the picture loading is complete.
*/
public function complete( e:Event ):void
{
trace("complete : " e) ;
addChild( loader ) ;
update() ;
}
 
/**
* Update the view of the display.
*/
public function update():void
{
 
graphics.clear() ;
graphics.beginFill(0xFFFFFF, 100) ;
graphics.drawRect(0, 0, w, h) ;
graphics.endFill() ;
 
if ( contains(loader) )
{
loader.x = ( w - loader.width ) / 2 ;
loader.y = ( h - loader.height ) / 2 ;
}
 
}
 
}
}

La classe PictureDisplay hérite de la classe AS3 flash.display.Sprite.

Pour les besoins de l'exemple j'ai déclaré 2 attributs w:Number et h:Number qui définissent la largeur et la hauteur utilisées pour dessiner le fond du conteneur. Le conteneur par défaut dessine un fond blanc de 260 par 260 pixels avec la méthode update() qui permet de rafraichir à tout moment la vue du conteneur.

La classe PictureDisplay contient également une attribut de type Loader qui servira de chargeur d'image bitmap externe le moment voulu. J'ai juste connecté cette référence avec son conteneur en abonnant l'objet qui écoute l'évènement Event.COMPLETE qui est notifié à la fin du chargement de l'image si tout se passe bien.

loader = new Loader() ;
loader.contentLoaderInfo.addEventListener( Event.COMPLETE, complete ) ;

La méthode "complete" attache juste le loader dans le conteneur si une image est chargée et ensuite remet à jour l'affichage de l'image chargée en la centrant par rapport aux propriétés de la largeur (w) et de la hauteur (h) de l'instance.

3 - Création des visitors de la classe PictureDisplay

Nous mettons en place maintenant 4 types de classes concrètes qui implémentent l'interface IVisitor. Ces classes sont des extensions de classe PictureDisplay, ces visiteurs servent de plugins qui apportent des fonctionnalités supplémentaires sans surcharger la classe principale.

3-1 - La classe ClearVisitor

Voici le contenu de la classe principale de l'application ClearVisitor :

package visitor.visitor
{
import andromeda.util.visitor.IVisitable;
import andromeda.util.visitor.IVisitor;
 
import visitor.display.PictureDisplay;
 
/**
* This visitor clear the view of a PictureDisplay instance.
* @author eKameleon
*/
public class ClearVisitor implements IVisitor
{
 
/**
* Creates a new ClearVisitor instance.
*/
public function ClearVisitor()
{
super();
}
 
/**
* Clear a PictureDisplay object.
* Visit the IVisitable object.
*/
public function visit( o:IVisitable ):void
{
var picture:PictureDisplay = o as PictureDisplay ;
trace( this " visit : " picture ) ;
if ( picture != null )
{
if ( picture.contains( picture.loader ) )
{
picture.removeChild( picture.loader ) ;
}
}
else
{
throw new Error(this " 'visit' method failed, the argument of this method must be a PictureDisplay instance.") ;
}
}
 
}
}

Ce visitor est très très simple car il permet juste de vider visuellement l'affichage d'une image chargée. Une référence de la classe ClearVisitor visite une référence de la classe PictureDisplay et essaie de supprimer la référence de son loader dans le conteneur avec la méthode removeChild().

if ( picture.contains( picture.loader ) )
{
picture.removeChild( picture.loader ) ;
}

la méthode removeChild() désabonne le loader mais ne la supprime pas totalement. Le loader devient donc invisible et sera attaché uniquement si une image est chargée totalement (voir méthode complete() dans la classe PictureDisplay).

3-2 - La classe HideVisitor

Voici le contenu de la classe principale de l'application HideVisitor :

package visitor.visitor
{
import andromeda.util.visitor.IVisitable;
import andromeda.util.visitor.IVisitor;
 
import visitor.display.PictureDisplay;
 
/**
* This visitor hide the PictureDisplay reference of the application.
*/
public class HideVisitor implements IVisitor
{
 
/**
* Creates a new HideVisitor instance.
*/
public function HideVisitor()
{
super();
}
 
/**
* Hide a Picture object.
* Visit the IVisitable object.
*/
public function visit(o:IVisitable):void
{
var picture:PictureDisplay = o as PictureDisplay ;
trace( this " visit : " picture ) ;
if ( picture != null )
{
picture.visible = false ;
}
else
{
throw new Error(this " 'visit' method failed, the argument of this method must be a PictureDisplay instance.") ;
}
}
 
}
}

Cette classe visite une référence de la classe PictureDisplay pour la faire disparaitre visiuellement en modifiant juste sa propriété visible.

L'exemple ici peut paraitre un peu trop simple et peu explicite, nous pourrions penser qu'il suffit de lancer directement une instruction simple picture.visible = false dans le code de l'application pour faire disparaitre la référence.

Cet exemple reste très simple car j'évite de le compliquer en ajoutant trop de concepts en même temps, ce qui rendrait peut être plus difficile la compréhension du Design Pattern Visitor...

... Mais je vous propose d'imaginer quelques instants une situation de dernière minute avec une évolution non prévue de l'application avec une nouvelle contrainte technique et graphique : ''L'image doit disparaitre avec un effet de fadding basé sur une Tween sur la propriété numérique alpha, mais aussi avec un léger effet de flou avec la classe flash.filters.BlurFilter"'.

Dans ce cas il sera très simple de créer un nouveau visitor ou de modifier rapidement le visitor existant en ajoutant une transition personnalisée sans avoir encore une fois à modifier la classe PictureDisplay.

Ce visitor nous montre donc qu'il va être possible de créer plusieurs visitors à l'avance ou au fur et à mesure des besoins. Ils proposeront des transitions différentes pour faire disparaitre une image dans notre application. Le code devient donc modulaire et réutilisable. Dans nos prochaines applications nous pourrons utiliser ces visitors sans soucis sans avoir à reprendre complètement tout le code.

3-3 La classe ShowVisitor

Voici le contenu de la classe principale de l'application "ShowVisitor" :

package visitor.visitor
{
import andromeda.util.visitor.IVisitable;
import andromeda.util.visitor.IVisitor;
 
import visitor.display.PictureDisplay;
 
/**
* This visitor show the PictureDisplay reference of the application.
*/
public class ShowVisitor implements IVisitor
{
 
/**
* Creates a new ShowVisitor instance.
*/
public function ShowVisitor()
{
super();
}
 
/**
* Clear a PictureDisplay object.
* Visit the IVisitable object.
*/
public function visit(o:IVisitable):void
{
var picture:PictureDisplay = o as PictureDisplay ;
trace( this " visit : " picture ) ;
if ( picture != null )
{
picture.visible = true ;
}
else
{
throw new Error(this " 'visit' method failed, the argument of this method must be a PictureDisplay instance.") ;
}
}
}
}

Identique à la classe HideVisitor, cette classe visite un objet de type PictureDisplay et affiche celui ci en donnant une valeur true à la propriété visible de l'instance.

Je ne vais pas reprendre l'explication de la classe HideVisitor pour ce nouveau visitor car elle est en tout point identique mais je propose à tous ceux qui sont un peu curieux de créer votre propre visitor basé sur celui ci est d'en profiter pour cabler à l'intérieur une transition un peu plus complexe en utilisant par exemple la classe pegas.transitions.Tween de l'extension PEGAS de VEGAS.

Rien de mieux qu'un peu de travail pratique pour comprendre complètement un concept.

3-4 - La classe LoaderVisitor

Cette classe est la plus importante dans notre exemple, c'est elle qui ajoute la fonctionnalité qui permet à la classe Picture de charger une image externe.

Voici le contenu de la classe principale de l'application "LoaderVisitor" :

package visitor.visitor
{
import flash.display.Loader;
import flash.net.URLRequest;
 
import andromeda.util.visitor.IVisitable;
import andromeda.util.visitor.IVisitor;
 
import visitor.display.PictureDisplay;
 
public class LoaderVisitor implements IVisitor
{
 
/**
* Creates a new LoaderVisitor instance.
* @param url The String representation of the full qualified file name to load.
*/
public function LoaderVisitor( url:String=null )
{
request = new URLRequest(url) ;
}
 
/**
* The URLRequest of the visitor.
*/
public var request:URLRequest ;
 
/**
* Indicates the url of this LoaderVisitor.
*/
public function get url():String
{
return request.url ;
}
 
/**
* @private
*/
public function set url(sUrl:String):void
{
request.url = sUrl ;
}
 
/**
* Loader a Picture object.
* Visit the IVisitable object.
*/
public function visit( o:IVisitable ):void
{
var picture:PictureDisplay = o as PictureDisplay ;
trace( this " visit : " picture ) ;
if ( picture != null )
{
picture.accept( new ClearVisitor() ) ;
picture.loader.load( request ) ;
}
else
{
throw new Error(this " 'visit' method failed, the argument of this method must be a PictureDisplay instance.") ;
}
}
 
}
}

Dans cette classe j'utilise le visitor très simplement en récupérant dans la méthode visit() la référence de la classe PictureDisplay et son loader nécessaire pour charger l'image externe.

4 - Code principal de l'application

Voici le contenu de la classe principale de l'application "Visitor" :

package
{
import flash.display.*;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
 
import visitor.display.PictureDisplay;
import visitor.visitor.HideVisitor;
import visitor.visitor.LoaderVisitor;
import visitor.visitor.ShowVisitor;
 
/**
* The main class of the visitor tutorial.
* @author eKameleon
*/
public class Visitor extends Sprite
{
 
/**
* Creates a new visitor instance.
*/
public function Visitor()
{
 
// stage
 
stage.scaleMode = StageScaleMode.NO_SCALE ;
stage.align = StageAlign.TOP_LEFT ;
 
stage.addEventListener(KeyboardEvent.KEY_DOWN , keyDownHandler ) ;
 
// view
 
picture = new PictureDisplay() ;
picture.x = 100 ;
picture.y = 100 ;
 
addChild( picture ) ;
 
// test
 
picture.accept( new LoaderVisitor( "library/picture1.jpg" )) ;
 
}
 
/**
* The picture display reference.
*/
public var picture:PictureDisplay ;
 
/**
* Invoked when a key is down.
*/
private function keyDownHandler( e:KeyboardEvent = null ):void
{
var code:uint = e.keyCode ;
trace("key down : " code) ;
switch( code )
{
case Keyboard.UP :
{
picture.accept( new HideVisitor() ) ;
break ;
}
case Keyboard.DOWN :
{
picture.accept( new ShowVisitor() ) ;
break ;
}
}
}
}
}

Dans cette classe le code est très simple :

En premier j'initialise les paramètres globaux de la scène principale (alignement, etc.).
Puis j'initialise la vue avec une instance de la classe PictureDisplay.
Enfin je défini l'url de l'image externe que je souhaite charger.
Pour finir nous pouvons tester les visitors HideVisitor et ShowVisitor en appuyant sur les touches UP et DOWN du clavier.

Dans Flash CS3 il suffit d'ajouter cette classe dans le panneau de propriété du document de votre fichier .fla pour tester l'exemple (voir fichier visitor.fla).

Conclusion

Je pense que ce petit refactoring du tutoriel original apporte un bon coup de jeune.

Je vous rappelle comme toujours pour ceux qui découvrent ici mon framework et ses implémentations que vous pouvez retrouver sur le wiki du Google Code de VEGAS le tutoriel complet sur comment installer les sources et exemples du projet : Install the VEGAS project with Subversion and TortoiseSVN.

Pour ceux qui ont des questions ou qui veulent suivre l'actualité de VEGAS et ses extensions vous pouvez vous abonner sur le Google Groups : VEGASoS

Je suis activement en cours de refactoring AS3 des tutoriels sur les Design Pattern MVC basés sur le DP Observer ou sur un FrontController. Ensuite je peux vous annoncer dans très peu de temps un tout nouveau tutoriel (en plusieurs parties ?) Sur mon implémentation du Design Pattern d'Inversion de contrôle (IoC) avec AndromedAS et un DOM au format eden.

Pour les plus impatients d'entre vous vous pouvez déjà regarder les sources de ces tutoriels dans la branche AS3 de VEGAS et pour les plus motivés d'entre vous... vous pouvre déjà consulter une ébauche de mon futur tutoriel IoC sur le wiki de AndromedAS : http://code.google.com/p/andromed-as/wiki/TutorialsAndromedA_IOC (en anglais pour le moment mais en cours d'écriture en français)

Je me rends compte que vu le nombre de tutoriels en cours et qui vont arriver il y a de forte chance que tout cela termine en bouquin opensource en complément du framework disponible en ligne sur Google Documents avec pleins d'autres surprises cet été

En attendant bonne lecture

PS : désolé pour les petites perturbations sur le blog ce week end, j'ai eu des problèmes de changement d'hébergement chez Amen et ensuite problème pour migrer mon blog sur DotClear2 mais maintenant tout semble ok. N'hésitez pas à me dire si vous trouvez des bugs sur mon blog [Less]

Page "La classe system.numeric.Mathematics"

Hello :)
Mise à jour du tutoriel sur la classe vegas.util.MathsUtil. Cette
classe devient dans la version AS3 de VEGAS la classe
system.numeric.Mathematics.
Comme vous pouvez le voir je participe activement avec Zwetan au ... [More]
développement du framework ES4a (Maashaack) et VEGAS se base
maintenant entièrement sur ce framework bas niveau ! [Less]

AST'r AS3 - eGallery example update

Hello :)
Information au sujet de AST'r en version AS3 qui voit l'exemple
eGallery évoluer avec mes dernières mises à jour du package ioc de
AndromedAS :)
Je vous conseille de mettre à jour le SVN du projet et d'aller ... [More]
rapidement voir le code et l'exemple qui se trouve dans AS3/trunk/
examples/egallery/trunk : [link] [Less]