[VEGAS] A - Design Pattern d'inversion de contrôle - Généralités
Par eKameleon le mardi, juillet 22 2008, 00:05 - VEGAS - Lien permanent
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 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://vegas.googlecode.com/svn/AS3/trunk/bin/test/andromeda/ioc/
- http://vegas.googlecode.com/svn/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 :
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 :
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 , properties : [ { 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 , properties : [ { 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.
Liens
Article suivant

Commentaires
WOW, impressionant
là tu as vraiment poussé l'usage de eden dans ses limites et c'est excellent
comme quoi la possibilité d'instancier des custom type dans eden ca reste une bonne feature qui c'est vrai aurait bien du mal a etre dupliqué en XML :p
pour le coté trop verbeux du XML, voir cet excellent article
http://www.codinghorror.com/blog/ar...
bravo
vivement les prochains articles
Moi j'ai la chance d'utiliser déjà l'IoC dans un projet pro avec ekameleon et j'avoue que c'est génial.
Le pattern est un peu déstabilisant au départ mais on s'y fait très vite et cela allège grandement le code source as3.
donc, les gars allez-y foncez la route est belle
++boost
Je n'ai pas encore les compétences pour appréhender les tenants et les aboutissants de ce pattern (alors de là à l'utiliser...) mais je reste impressionner par la somme de travail que représente Vegas et la maintenance des sites qui héberge le projet (excellents tutoriaux).
Bravo.
Salut!
Bravo pour le travail accompli et l'article.
Si ta demande est toujours d'actualité, je peux t'aider pour la traduction en anglais de la documentation et des articles.
A+ et encore bravo!
David
Merci
Oui ma proposition pour m'aider à traduire en anglais mes articles est toujours d'actualité
Si cela t'intéresse tu peux m'envoyer un mail (voir mail dans toutes mes classes du framework
ex : http://svn1.cvsdude.com/osflash/veg... )
Je te filerai un lien vers la version en cours de rédaction sur Google Documents de mon article en VF et un autre lien vers un espace partagé pour travailler sur la version anglaise
Dans tous les cas les articles sont déjà public en lecture pour ceux que cela intéressent... je ne donne pas tout de suite les url sur mon blog car je bosse encore dessus et c'est parfois pas très propre
EKA+
Eka, bravo ! Ton article est vraiment très clair et instructif. Du coup, il me rend impatient… à bon entendeur
Merci.
Vraiment très intéressant comme article/tutoriel. Je suis assez impressionné par ce que permet de faire le pattern IoC et la façon dont il est implémenté dans Vegas.
Dans le dernier exemple, je me pose la question de savoir, si en plus d'être parsé et les définitions d'objet ajouté à la fabrique IoC la construction de l'application est elle lancée ?
Je ne sais pas si c'est ainsi qu'il faut voir les choses, mais de ce que je comprends on charge des définitions et dépendances d'objets dans une fabrique. mais à quelle moment la fabrique se met elle en route ? C'est à dire lance la création des objets ?
En tout cas bravo et surtout merci pour ca !
PS : Vegas et ses extensions semblent représenter une grande quantité de classes. Est il possible de visualiser quelque part un diagramme des classes pour se donner une idée des dépendances ?
Hello
Pour ce qui est de la fabrique et du lancement automatique de certains objets via les définitions d'objets définies dedans, j'explique cela dans le prochain chapitre que je suis en train de finaliser
Sinon pour résumer.. tu as du remarquer que certaines définitions d'objets ont le tag "singleton", et par défaut dans une fabrique IoC toutes les définitions d'objets "singleton" génèrent automatiquement leur objet singleton pendant l'initialisation de la fabrique (juste après le chargement du contexte externe par exemple). Du coup en créant convenablement certaines définitions (comme la définition "root" dans les exemples de l'article) on peut générer un "lanceur" d'application efficace sans se prendre la tête).
A noter qu'il est possible de brider la création automatique des singletons avec un attribut "lazyInit" dans les définitions d'objets ... pareil à voir dans le prochain article
Pour VEGAS sinon je te propose de lire la documentation :
http://www.ekameleon.net/vegas/docs...
Pas de représentation par diagramme du framework mais j'essaierai de voir dans les prochains jours si je peux en faire une
(Va être grosse.. faudra faire des petites représentations selon les cas ;))
EKA+
mais c'est un travail de fou que tu as fait ! Ôo
si j'ai bien compris, l'inversion de contrôle sert à "programmer ton code" c'est ca ?
mis à part que là c'est fait de façon très complète et étendue, j'ai du mal à distinguer la différence avec un modèle "moteur/données logiques".
pourrais-tu m'éclairer un peu s'il te plait ?
Hello
Un framework IoC de base se charge de créer les dépendances entre chaque objet à la place du développeur et permet un "programmation en couches" simple de tous les éléments avec le moins de dépendances entre les objet.
Il ne sert pas du tout à coder à ta place, même si permet pas mal de chose. Il est vrai et que certains ont tendances à penser qu'il suffit d'utiliser celui ci pour tout faire
Il faudra toujours coder des classes, des composants, etc.. pour créer une application efficacement avec cet outil. C'est pour cela que VEGAS et ses extensions sont là et mettent à disposition tous les outils composants/graphiques/serveur etc. pour créer une RIA Complète, l'IoC ne fait vraiment pas tout
Le moteur IoC sert de noyau dure pour créer une application et tous les outils dont il dispose permet d'optimiser le flux de production avec des outils intégrés de localisation, configuration, chargements de dll externes etc... Tout cela facilite franchement le travail.
Pour ce qui est d'un moteur de modèle "moteur/données logiques" tu peux préciser ce que tu entends par là ?
A noter dans les prochaines parties beaucoup d'explication sur les différentes possibiltiés du framework mais je vous invite à regarder de prêt en attendant tous les exemples contenus dans les répertoires AS3/trunk/bin/test/andromeda/ioc et AS3/trunk/bin/test/asgard/net
A noter aussi que mon projet AST'r en version AS3 (framework template ActionScript basé sur VEGAS) montre en grandeur nature comment utiliser l'IoC de VEGAS avec un exemple basique et un exemple d'utilisation avec une petite galerie d'image dynamique basée sur des values objets chargés avec un fichier eden ou une base de donnée via AMFPHP ! (Je pense sérieusement ajouter en 4 ème partie de mon article ); une explication complète de ce module .. en quelque sorte un exercice concret basé sur tout ce qui sera expliqué dans les 3 parties de bases de cet partie du e-book en cours de rédaction
EKA+
"moteur/données logiques" c'était une expression un peu maladroite d'exprimer une conception du programme en deux parties:
-d'un coté tu développes le moteur (plus ou moins générique) de l'application, tel que tu le livrerais à un client (au sens humain du terme). le moteur consiste en un (ou plusieurs) algorithmes de base, accompagnés d'un framework/api qui permettent d'en tirer pleinement partie. le moteur seul ne peux pas tourner, il sert à "faire vivre" les données développées par le client (qui est ou non la même personne).
-d'un autre coté il y a ces même données. "donnée" c'est pas le bon terme puisqu'il s'agit d'objets actifs (enfin plus "actionnés" que "actifs", à la façon du hollywood principle).
(désolé si j'ai pas été très clair, c'est une méthode que j'utilise mais que je n'avais jamais expliquée avant.)
en fait ton framework accélèrerait et faciliterait en effet tout le travail que ma méthode oblige à refaire à chaque fois. j'ai pas encore assez d'expérience pour en avoir ressenti le besoin =P
reste à voir ce qui se fait avec d'autres langages =D merci en tout cas.
Hello
Dans d'autres langages ? c'est simple suffit de voir ce qui se dit à propos des frameworks Spring.NET et Spring JAVA :
http://www.springframework.org/
et de lire l'excellent article :
http://www.dotnetguru.org/articles/dossiers/ioc/ioc.htm
Maintenant je te conseille vivement de regarder mes exemples dans AST'r qui indiquent comment utiliser un pattern MVC via FrontController avec chargement de donnée avec service remoting et récupération de ValueObjects + utilisation du pattern Locator etc.. Tu verras comment utiliser mon framework pour justement créer finalement un projet complet pour n'importe quel client
Sinon dans tous les cas la suite dans le prochain épisode
EKA+
Salut,
Je pense que j'irai voir l'example de la galerie photo, poura vior une vue d'ensemble. Car là il est vrai qu'on est plongé d'un coup dans un processus qui semble complexe et dont on ne devine aps encore toute l'étendue.
PS: j'avoue que je suis toujours perdue avec tout ce que représente Vegas : adromeda, asgard, astrr etc..
Qui fait quoi, qui sert à quoi etc.
Tu dis que AST'R est un framework de template, qu'entends tu par là , J'ai du mal à concevoir ? (je sais, faut que j'aille voir directement l'exemple ....)
Hello
Un framework "documentaire" permet comme son nom l'indique de fournir des exemples d'implémentations pour utiliser et maintenir un framework applicatif.
AST'r contient en gros un "template de code" en fournissant en gros une structure que l'on peut pratiquement copier/coller pour créer une base d'application super rapidement.
Personnellement AST'r me permet :
1 - documenter et expliquer comment fonctionne mon framework aux nouvelles équipes avec qui je bosse
2 - maintenir à tout moment un exemple de code qui évolue en même temps que le code source principal de VEGAS et ses extensions. Ce qui permet avec le subversion et les issues du google code d'indiquer aux gens qui suivent le projet ce qui change et de montrer tout de suite comment faire évoluer leurs applications en fonction des nouvelles fonctionnalités ou des mises à jour des outils.
3 - De créer une nouvelle application super rapidement en utilisant le code qui se trouve dans le src/ principal de la branche AS2 ou AS3 de AST'r
.. en gros ... je fais avec mon client SVN un "export SVN" du src/ et du deploy/ de AST'r dans le src de mon projet et ensuite avec FDT en moins d'une minute j'ai une application prête à être codée avec : un moteur de localisation, un moteur de config, chargement d'une feuille de style, chargement de polices de caractères externes, moteur IoC pour gérer mes dépendances, etc....
Donc AST'r est un framework qui contient tous les éléments pour aller plus loin avec VEGAS .. beaucoup plus intéressant finalement que les petits exemples contenus dans VEGAS (AS3/trunk/bin/test).
PS : D'ici la fin de l'été ou au pire à la rentrée je mettrais dans AST'r une petite application opensource codée en IoC AS3 qui contiendra une bibliothèque de vidéo hébergée sur YouTube contenant des tutoriaux vidéos sur l'utilisation de VEGAS et ses extensions
je trouve sympa de faire l'appli en opensource, de la documenter et qu'elle contienne elle même des vidéos qui explique comment la faire lol
Attention tout de même.. pour bien comprendre certaines notions il est préférable souvent de passer
par le répertoire d'exemple dans les sources de VEGAS car ils contiennent tout de même des tas d'exemple pour Flash (AS2 ou AS3) qui permettent de se familiariser facilement avec le code en isolant chaque concept (le plus possible).
Sinon pour ce qui est du contenu des extensions.. suffit de lire le contenu de la page officielle de VEGAS sur Google Code (voir lien au début de l'article)
Pour toute question sinon je le répète il y a mon Google Groups (voir lien en haut de l'article) et je répondrai sans soucis à toutes tes questions ou celles de ceux qui sont intéressé par VEGAS
EKA+
Je reviens de vacance et je m'applique!
Merci pour ce tutoriel très bien expliqué.
Petit à petit, je commence à comprendre la puissance de ton framework.
Apparemment cela correspond exactement au cahier des charges de mon nouveau projet.
Je srais bien intéressé par ta doc en fr (celle de Google Document) afin de travailler au maximum la compréhension de ton framework.
Encore merci pour tout le travail que tu fournis en open source et ton aide.
PS : FDT avec le compilateur flex (bien comfiguré) c'est le top sous linux et rapide en plus
hello
je viens de mettre les parties A, B et C en public :
http://groups.google.com/group/vega...
Quand j'aurai une partir D totalement opérationnelle (me reste la partie sur les ressources de type i18n à finir) je ferai un petit article sur mon blog
EKA+