IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

VEGAS - Design Pattern d'inversion de contrôle : Définitions d'objets

Date de publication : 11/09/2009


II. Description des attributs de base d'une définition d'objet.
II-A. L'attribut "id"
II-B. L'attribut "type"
II-C. L'attribut "arguments"
II-C-1. L'attribut "value"
II-C-2. L'attribut "ref"
II-C-3. L'attribut "config".
II-C-4. L'attribut "locale".
II-C-5. L'attribut "properties"
II-D. L'attribut "properties"
II-D-1. Utilisation de l'attribut "value".
II-D-2. Utilisation de l'attribut "ref".
II-D-3. Utilisation de l'attribut "config".
II-D-4. Utilisation de l'attribut "locale".
II-D-5. Définition d'une propriété de type Function et utilisation de l'attribut "arguments"
II-D-6. L'attribut "evaluators".
II-E. Les attributs "scope", "singleton"et "lazyInit".
II-E-1. L'attribut "scope"
II-E-2. L'attribut "singleton"
II-E-3. L'attribut "lazyInit"
II-F. L'attribut "listeners"


II. Description des attributs de base d'une définition d'objet.


II-A. L'attribut "id"

Cet identifiant est une clé unique obligatoire utilisée pour obtenir la référence d'un objet défini dans le conteneur IoC.
Exemple

id  : "user" ,
Regardons maintenant comment utiliser l'identifiant unique d'une définition d'objet pour créer et obtenir avec la fabrique l'objet correspondant :

var user:User = factory.getObject( "user" ) ;
 
trace( user ) ;
L'attribut "id" est indispensable pour créer un objet avec la méthode getObject() mais aussi pour abonner une nouvelle définition d'objet dans la fabrique.

Petite astuce :

J'utilise souvent dans mes projets des classes contenant des énumérations sous forme de constantes statiques pour lister l'ensemble des identifiants importants utilisés par mes définitions d'objets dans mes fabriques IoC. J'utilise cette technique surtout pour les définitions d'objets ayant un scope singleton.

Par exemple, nous pouvons créer une énumération des vues principales d'une application en créant rapidement une petite classe DisplayList :

package test
{
 
    public class DisplayList
    {
 
         public static const BACKGROUND:String = "background" ;
 
         public static const BODY:String       = "body" ;
 
         public static const MENU:String       = "menu" ;
 
     }
 
}
Ensuite nous pouvons créer un contexte de configuration IoC avec les définitions d'objets suivantes :

objects =
[
 
    {  
        id        : "background"  ,
        type      : "flash.dislay.Sprite" ,
        singleton : true
    }
    ,
    {  
        id        : "body"  ,
        type      : "flash.dislay.Sprite" ,
        singleton : true
    }
    ,
    {  
        id        : "menu"  ,
        type      : "flash.dislay.Sprite" ,
        singleton : true
    }
] ;
Pour appeler, à tout moment, un des singletons définis ci-dessus, il suffira de taper le code suivant :

import andromeda.ioc.factory.ECMAObjectFactory ;
 
import flash.display.Sprite ;
 
import test.DisplayList ;
 
var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
factory.create( objects ) ;
 
var background:Sprite = factory.getObject( DisplayList.BACKGROUND ) as Sprite ;

II-B. L'attribut "type"

Cet attribut est obligatoire et indique en général le nom complet de la classe (ou type) de l'objet que l'on souhaite créer ou cibler dans la fabrique (cas d'un objet "singleton" par exemple).

type  : "test.User" ,
Il faut s'assurer que la classe existe dans le fichier principal de l'application. Si cette classe n'existe pas, il est possible d'inclure cette classe (ou les éléments graphiques, les sons, etc.) en utilisant le moteur de chargement de ressources externes de la fabrique IoC courante. Sinon il faudra forcer l'existence de cette classe en l'appelant au moins une fois dans le code principal de l'application.

A noter que l'expression (String) de cet attribut peut être soumise à des filtrages et formatages en utilisant des outils câblés dans la configuration de la fabrique, voir à ce sujet le chapitre C-1.7. Ces évaluations et filtrages permettent de simplifier grandement l'écriture de vos définitions d'objets.

Remarque :

Si nous utilisons dans une définition d'objet une stratégie de création différente de celle utilisée par défaut dans le conteneur léger, l'objet généré à partir de cette définition d'objet peut avoir un type "différent" de celui défini dans la définition d'objet mais devra avoir un "casting" compatible avec ce type établi avec l'attribut "type".

Il faut donc que l'objet renvoyé par la fabrique hérite ou implémente le type défini dans sa définition d'objet. Le mot clé "as" est utilisé dans la fabrique juste après la création d'un nouvel objet et il assure que l'objet généré possède bien le bon type. En cas de problème et d'incompatibilité sur le typage défini dans la définition d'objet avec le type de l'objet créé par la fabrique, le conteneur renverra un objet "null".


II-C. L'attribut "arguments"

Dans les définitions d'objets l'attribut "arguments" est une collection (Array) d'objets génériques qui permet de définir les paramètres que l'on souhaite passer dans la fonction constructeur d'une classe ou dans une méthode.

Cet attribut peut être utilisé à la racine d'une définition d'objet, alors il correspond à la liste des paramètres de la fonction constructeur de la classe utilisée pour créer l'objet, sinon (exemple dans l'attribut "properties") il correspondra à la liste des arguments que l'on souhaite passer dans une méthode.

Cet attribut n'est pas obligatoire, toute fonction constructeur ou toute méthode définie sans cet attribut sera invoquée sans paramètre.

Dans AndromedAS, cet attribut permet de créer et d'initialiser des objets de type andromeda.ioc.core.ObjectArgument.

Cette classe contient essentiellement les attributs suivants :

  • value : Correspond à la valeur ou référence de l'argument. Cet attribut peut être soit directement la valeur de l'argument, soit une expression (String) qui sera interprété par la fabrique en fonction de la valeur de l'attribut "policy" de l'instance.
  • policy : Permet de définir la stratégie utilisée par la fabrique pour créer l'argument, il détermine si l'argument est défini avec une valeur simple, une référence vers une autre définition d'objet, etc.
  • evaluators : (optionnel) Permet de définir dans un cadre bien particulier une collection d'objects d'évaluation qui sera appliquée pour filtrer la valeur ou référence définie dans l'objet de type ObjectArgument.
La classe ObjectArgument contient comme la classe ObjectDefinition une méthode statique create( init:Object ) qui lui permet de générer simplement une liste (Array) d'objet de type ObjectArgument en l'initialisant avec liste (Array) d'objets génériques. Tous les objets génériques seront transformés en instance de type ObjectArgument.
Exemple

import andromeda.ioc.core.ObjectArgument ;
import andromeda.ioc.core.ObjectAttribute ;
 
var args:Array =
[
    { value  : "hello world"  } ,
    { ref    : "my_id"        } ,
    { config : "config.value" } ,
    { locale : "locale.value" } ,
 
    { value  : "hello world" , evaluators:["myEvaluator"] }
] ;
 
args = ObjectArgument.create( args ) ; // transform all arguments with the ObjectArgument factory.
 
trace( args[0] is ObjectArgument ) ; // true
 
trace( args[0].policy == ObjectAttribute.VALUE     ) ; // true
trace( args[1].policy == ObjectAttribute.REFERENCE ) ; // true
trace( args[2].policy == ObjectAttribute.CONFIG    ) ; // true
trace( args[3].policy == ObjectAttribute.LOCALE    ) ; // true
 
trace( args[4].evaluators ) ; // myEvaluator
Les objets génériques doivent absolument être définis avec un attribut obligatoire spécifique pour être transformé en objet de type ObjectArgument sinon il ne sera pas pris en compte dans la méthode statique create() et sera remplacé par un objet null.

Nous pouvons observer dans l'exemple ci-dessus que tous les objets génériques sont définis avec un des attribut ci-dessous :

  • value
  • ref
  • config
  • locale
Ces attributs doivent correspondrent aux valeurs possibles de l'attribut policy de la classe ObjectArgument définies dans la classe ObjectAttribute avec les valeurs suivantes :

  • ObjectAttribute.VALUE : "value"
  • ObjectAttribute.REFERENCE : "ref"
  • ObjectAttribute.CONFIG : "config"
  • ObjectAttribute.LOCALE : "locale"
Chacun des types ci-dessus donnera l'information qu'il faut au conteneur pour générer l'argument en utilisant la stratégie nécessaire. Ces types sont stockés dans la propriété policy de chaque objet de type ObjectArgument renvoyé par la méthode statique create() de la classe.

Reprenons l'exemple principal de ce chapitre en isolant dans la définition d'objet "user" l'attribut principal "arguments" :

{ 
 
    id         : "user" ,
    type       : "test.User" ,
    arguments  :
    [
        { value : "eKameleon" } ,
        { value : "ALCARAZ"   } ,
        { ref   : "address"   }
    ]
}
Comme nous le voyons ci-dessus, les arguments sont définis par des objets génériques qui contiennent en général un attribut "value" ou un attribut "ref". Un seul de ces attributs peut être utilisé pour un même objet générique. Ces attributs permettent de définir la valeur d'un argument ou permettent de cibler une référence en ciblant une autre définition d'objet dans le conteneur IoC.

L'exemple précédent correspond au code ActionScript suivant :

import test.User ;
import andromeda.ioc.factory.ECMAObjectFactory ;
 
var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
var user:User = new User( "eKameleon" , "ALCARAZ" , factory.getObject("address") ) ;
Utilisé dans l'attribut "properties", l'attribut "arguments" sera pris en compte par le conteneur uniquement si la propriété définie dans un des objets génériques de cette collection sera de type "Function" (méthode de l'objet défini dans la fabrique).
Exemple

properties :
[
     { name : "setMail" , arguments : [ { value : "ekameleon [at] gmail.com" } ] }
]
Cet exemple très simple permet de définir dans l'attribut "properties" une méthode "setMail()" qui sera invoquée lors de l'initialisation de l'objet.

info A noter que nous pouvons utiliser à la place des attributs classiques "value" et "ref", deux autres attributs "spéciaux" : "config" (voir chapitre B-1.3.3) et "locale" (voir chapitre B-1.3.4). Ces attributs sont très spécifiques car ils entrainent une évaluation de la valeur passée en argument pour récupérer une valeur "localisée" ou "configurée" dans l'application.

II-C-1. L'attribut "value"

Cet attribut utilisé dans les définitions d'un argument est le plus simple :

{ value : "eKameleon" }
Cette solution permet de définir un argument avec une simple valeur de n'importe quel type.


II-C-2. L'attribut "ref"

Cet attribut permet de définir une expression (String) qui correspondra à l'identifiant d'une définition d'objet enregistrée dans le conteneur.

{ ref : "address" }
Cet attribut permet de définir les dépendances entre les objets définis dans le conteneur IoC.

Dans l'exemple ci-dessus, la référence "address" sera utilisée au moment de créer l'objet et la fabrique invoquera la méthode getObject("address") pour récupérer (ou créer) la référence de l'objet dont elle dépend.

Tous les attributs "ref" utilisés dans les définitions d'objets ont le même mode de fonctionnement. Je vais donc essayer de généraliser ici l'utilisation de cet attribut que nous pourrons retrouver par exemple dans les attributs "properties", etc.

Cet attribut possède donc une expression évaluée dans la fabrique par un objet de type andromeda.ioc.evaluators.ReferenceEvaluator.

La classe ReferenceEvaluator cible une fabrique IoC et analyse une chaine de caractère. Cette classe évalue donc cette expression et essai en priorité d'identifier une définition d'objet contenue dans la fabrique IoC pour renvoyer tout de suite la référence de l'objet correspondant.

Si l'expression est définie avec une notation pointée il sera alors possible de cibler un objet dans la fabrique et de récupérer la valeur d'un de ses attributs.
Exemple

{ ref : "address.city"   
L'argument défini avec la notation ci-dessus aura pour valeur "Marseille". A noter qu'il est possible d'utiliser une notation pointée plus profonde pour chercher une valeur définie plus plus loin dans les attributs de l'objet, exemple "a.b.c.d....".

L'attribut "ref" peut être défini également avec des "expressions magiques", ces chaines de caractères spéciales permettent de cibler par référence certains objets importants définis au préalable dans la configuration de la fabrique IoC.

  • #this : Permet de cibler la référence de la fabrique locale utilisée par la définition d'objet courante.
  • #root : Permet de cibler la référence de la scène principale de l'animation courante.
  • #stage : Permet de cibler la référence de l'objet stage de l'application.
Note :

Pour définir convenablement les expressions magiques #root et #stage, il faut définir manuellement la propriété "root" de l'objet de configuration de la fabrique IoC courante en ciblant si possible la référence de plus bas niveau de l'application principale, nous pourrons par exemple écrire le code suivant dans la fonction constructeur de la classe principale de l'application :

import andromeda.ioc.factory.ECMAObjectFactory ;
 
var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
factory.config.root = this ;
Voir à ce sujet des exemples dans les chapitre A.3-2 et C.1-6.


II-C-3. L'attribut "config".


{ config : "author" }
Cet attribut spécial peut être défini à la place de l'attribut "value" ou de l'attribut "ref" pour cibler une valeur définie dans l'objet de configuration de la fabrique (voir chapitre C-1-1).


II-C-4. L'attribut "locale".


{ locale : "message" }
Cet attribut spécial peut être défini à la place de l'attribut "value" ou de l'attribut "ref" pour affecter sur la propriété une valeur définie dans l'objet de localisation courante de la fabrique (voir chapitre C-1-4).

Cet objet est très important car dans une utilisation avancée des fichiers de configurations du conteneur IoC, il est possible de charger des données localisées et de les utiliser le moment voulu par propagation évènementielle ou plus simplement avec l'attribut "locale" dans les arguments d'une définition d'objet. En fonction de la langue courante choisie dans l'application l'attribut locale renverra une valeur spécifique et localisée.


II-C-5. L'attribut "properties"

L'attribut "evaluators" est un attribut optionnel dans tous les objets génériques définis dans l'attribut "arguments" d'une définition d'objet. Plus d'informations à ce sujet à la fin de ce chapitre (cf. Partie 4 de ce chapitre).


II-D. L'attribut "properties"

Dans l'objet de configuration d'une fabrique IoC, l'attribut "properties" est une collection (Array) d'objets génériques simples qui permet d"initialiser un objet en définissant les valeurs de certains de ses attributs et en d'invoquant certaines de ses méthodes.

Dans l'objet de configuration une définition d'objet il est possible de définir chacun des attributs que l'on souhaite initialiser avec un object générique simple :

properties :
[
    { name:"age"  , value : 31        } ,
    { name:"job"  , ref   : "job_dev" }
]
Les objets génériques définis dans la collection ci-dessus nécessitent un attribut "name" qui indique quel attribut nous souhaitons initialiser.

Ensuite nous pourrons définir la valeur d'un attribut en utilisant les attributs "value", "ref", "config" ou "locale" et dans le cas d'une méthode nous pourrons utiliser un attribut "arguments" pour pour définir en cas de besoin ses paramètres.


II-D-1. Utilisation de l'attribut "value".


{ name:"age"  , value : 31  }
Cet attribut permet d'initialiser l'attribut d'un objet avec une valeur de n'importe quel type.


II-D-2. Utilisation de l'attribut "ref".


{ name : "job"  , ref : "job_dev" }
L'attribut "ref" est une une chaine de caractère (String) qui cible principalement une autre définition d'objet dans le conteneur IoC courant.

Comme dans l'attribut "arguments", il est possible d'aller plus loin au moment de définir l'expression de l'attribut "ref". Cette expression est évaluée par la fabrique avec la classe ReferenceEvaluator.
Exemple

{  
    id         : "job_dev"   ,
    type       : "test.Job"  ,
    properties : [ { name:"name" , value:"AS Developper" } ]
}
,
{ 
    id         : "field"   ,
    type       : "flash.text.TextField"  ,
    properties :
    [
         { name:"autoSize" , value : "left"         } ,
         { name:"text"     , ref   : "job_dev.name" }
    ]
}
Cet exemple très simple affiche le nom du métier défini dans la définition d'objet "job_dev" dans un champ de texte dynamique simple. L'attribut "ref" prend pour valeur une expression avec une notation pointée, le premier élément dans cette expression correspond à l'identifiant d'une définition d'objet dans la fabrique et une fois cette référence identifiée dans la fabrique la propriété "text" du champ de texte prend la valeur de la propriété name de l'objet de type test.Job.

idea Je vous conseille de consulter le descriptif plus complet du chapitre B.1-3-2 précédent (sur l'attribut "arguments") pour en savoir plus.

II-D-3. Utilisation de l'attribut "config".


{ name : "name"  , config : "author" }
Cet attribut spécial peut être défini à la place de l'attribut "value" ou de l'attribut "ref" pour cibler une valeur définie dans l'objet de configuration de la fabrique (voir chapitre C-1-1).


II-D-4. Utilisation de l'attribut "locale".


{ name : "name"  , locale : "message" }
Cet attribut spécial peut être défini à la place de l'attribut "value" ou de l'attribut "ref" pour affecter sur la propriété une valeur définie dans l'objet de localisation courante de la fabrique (voir chapitre C-1-4).


II-D-5. Définition d'une propriété de type Function et utilisation de l'attribut "arguments"

L'attribut "properties" permet également d'invoquer une ou plusieurs méthodes pendant l'initialisation de l'objet.

properties :
[
     { name : "setMail" , arguments : [ { value : "ekameleon [at] gmail.com" } ] }
]
Dans le cas de figure où nous souhaitons invoquer une méthode, nous pouvons définir un objet générique identique à ceux utilisés pour les attributs avec un attribut "name" indispensable pour définir le nom de la méthode que l'on souhaite invoquer. Il est possible aussi de déclarer un attribut "arguments" facultatif.

  • L'attribut "name" représente tout simplement le nom de la méthode à invoquer.
  • L'attribut "arguments" est une liste (Array) des arguments passés dans la méthode.
L'attribut "arguments" possède le même mode de fonctionnement que celui utilisé pour initialiser les paramètres de la fonction constructeur d'une définition d'objet (voir chapitre B.1-3).


II-D-6. L'attribut "evaluators".

L'attribut "evaluators" est un attribut optionnel dans tous les objets génériques définis dans l'attribut "properties" d'une définition d'objet. Plus d'informations à ce sujet à la fin de ce chapitre (chapitre B-4).


II-E. Les attributs "scope", "singleton"et "lazyInit".

Quand nous créons une définition d'objet nous mettons en place en quelque sorte une "recette" qui permet de créer un objet dans l'application.

L'idée de "recette" est importante, elle indique qu'une définition d'objet permet de créer un grand nombres d'instances dans l'application en utilisant des stratégies plus ou moins complexes de création et d'initialisation. Il est donc possible de créer plusieurs objets avec une définition d'objet unique.

Ces objets auront des cycles de vies particuliers et parfois devront avoir une représentation unique ou multiple dans l'application.

La notion de scope est importante dans un conteneur IoC car elle indique à la fabrique comment créer les objets de l'application et surtout comment les référencer par la suite.


II-E-1. L'attribut "scope"

L'attribut "scope" n'est pas obligatoire dans une définition d'objet et prend pour valeur une chaine de caractère (String).

scope : "prototype"
Voici les différentes valeurs de l'attribut "scope" dans les définitions d'objet :

  • "prototype"
  • "singleton"
Le scope "prototype" indique que la fabrique IoC peut créer avec la même définition d'objet un nombre indéfini d'instances.

import andromeda.ioc.factory.ECMAObjectFactory ;
 
import test.User ;
 
var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
var user1:User = factory.getObject("user") as User ;
 
var user2:User = factory.getObject("user") as User ;
 
trace( user1 == user2 ) ; // false
Le scope "singleton" indique que la définition d'objet peut créer une instance unique (singleton) et ensuite la mémorise dans le conteneur IoC. Par la suite chaque appel de cette même définition d'objet reverra une simple référence du singleton enregistré dans le cache de la fabrique.

Par défaut, toutes les définitions d'objets ont le scope "prototype" mais il est possible de modifier cette valeur avec les valeurs définies ci-dessus.

Vous pouvez retrouver dans AndromedAS une classe statique qui énumère les différents types de "scope" existant avec la classe andromeda.ioc.core.ObjectScope :

  • ObjectScope.PROTOTYPE
  • ObjectScope.SINGLETON
A noter que l'attribut "scope" dans une définition d'objet écrase toujours la valeur de l'attribut "singleton" dans cette même définition d'objet si il est utilisé. (voir chapitre B-1.5.2)


II-E-2. L'attribut "singleton"

Les objets génériques d'initialisation des définitions d'objets peuvent contenir un attribut "singleton" booléen. Cet attribut permet de modifier rapidement l'état par défaut de la propriété scope d'une définition d'objet en lui attribuant directement la valeur "singleton". Cet attribut est donc suffisant pour transformer rapidement la nature par défaut d'une définition d'objet dans un conteneur léger.

singleton : true
Si la définition d'objet est définie avec un scope "singleton" et que cet attribut possède la valeur "true" alors l'instance d'objet créé via la fabrique est sauvegardée dans le cache du conteneur et sera utilisée par la suite à chaque appel de la même définition d'objet.

Si pour un identifiant dans la fabrique IoC une définition d'objet est initialisée comme "singleton" alors à chaque appel de l'identifiant id dans la méthode getObject() de la fabrique renverra une référence singleton unique. La première fois où la méthode getObject() sera invoquée, la référence unique sera créée, initialisée et stockée en mémoire dans la fabrique.

Voyons un petit exemple de ce scope avec une définition d'objet singleton :

{ 
    id         : "user" ,
    type       : "test.User" ,
    arguments  :
    [
        { value : "eKameleon" } ,
        { value : "ALCARAZ"   } ,
        { ref   : "address"   }
    ] ,
    singleton   : true
}
Voyons ensuite dans un script ActionScript comment récupérer plusieurs fois une même référence de l'objet défini avec l'identifiant "user" dans la fabrique :

import andromeda.ioc.factory.ECMAObjectFactory;
 
import test.User ;
 
var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
var user1:User = factory.getObject("user") as User ;
 
var user2:User = factory.getObject("user") as User ;
 
trace( user1 == user2 ) ; // true

II-E-3. L'attribut "lazyInit"

Cet attribut est un booléen optionnel dans les définitions d'objets, il prend la valeur false par défaut.

lazyInit : true , // only if this object is a singleton
Cet attribut s'applique uniquement sur les définitions d'objets ayant un scope "singleton". Si vous utilisez cet attribut sur une définition d'objet non singleton il sera tout simplement ignoré.

Si cet attribut est false ou non défini, l'objet singleton sera automatiquement instancié pendant l'initialisation de la fabrique.

Si cet attribut est true alors l'objet singleton ne sera pas instancié automatiquement et l'objet singleton sera créé uniquement lors du premier appel de la méthode getObject().

Dans le cycle de vie d'une application, il peut s'avérer très pratique de créer les objets singletons au lancement de l'application, car ces objets peuvent lancer certains processus automatiquement sans avoir à utiliser un code en particulier dans l'application. Cette fonctionnalité permettra aussi de placer en mémoire certains objets qui seront disponibles beaucoup plus rapidement par la suite.

Malgré tout, l'attribut "lazyInit" peut s'avérer fort important si un objet singleton défini dans la fabrique ne doit absolument pas être instancié au lancement de l'application. Nous pouvons à ce sujet noter les raisons suivantes :

  • Ne pas surcharger la mémoire avec un singleton non nécessaire tout de suite au lancement de l'application.
  • Ne pas créer tout de suite le singleton car certaines de ses dépendances n'existent pas ou ne peuvent pas être instanciées dans le conteneur IoC pour le moment.

II-F. L'attribut "listeners"

L'attribut "listeners" d'une définition d'objet est une collection (Array) d'objets génériques qui permettent d'initialiser un objet pour que certaines de ses méthodes servent de fonctions écouteurs dans le modèle évènementiel de l'application.

Avec cet attribut l'objet est automatiquement abonné pour recevoir des évènements diffusés par un ou plusieurs diffuseurs d'évènements définis eux aussi dans la fabrique IoC.

A noter que si l'objet peut lui même diffuser des évènements (si il implémente l'interace flash.events.IEventDispatcher) il pourra s'abonner à lui même pour écouter la propagation de ses propres évènements .

Voyons maintenant un exemple concret d'utilisation de cet attribut avec une petite classe test.Listener qui possède une méthode change(e:Event). Cette méthode peut intercepter des évènements de type flash.events.Event.

package test
{
 
    import flash.events.Event;
 
    public class Listener
    {
 
        public function Listener()
        {
            //
        }
 
        public function change( e:Event ):void
        {
            trace( this + " change" + e ) ;
        }
 
    }
}
Voyons maintenant comment câbler un diffuseur d'évènement et son écouteur dans les définitions d'objets d'un conteneur IoC.

var context:Object =
[
    {
        id        : "dispatcher" ,
        type      : "flash.events.EventDispatcher" ,
        singleton : true
    }
    ,    
    {
        id        : "listener"   ,
        type      : "test.Listener" ,
        singleton : true ,
        listeners :
        [
            { dispatcher:"dispatcher" , type:"change" , method:"change" , useCapture:false, priority:0 , useWeakReference:true }  
        ]
    }    
] ;
 
var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
factory.create( context ) ;
 
var dispatcher:EventDispatcher = factory.getObject("dispatcher") ;
 
dispatcher.dispatchEvent( new Event( "change" ) ) ; // [object Listener] change [Event type="change" bubbles=false cancelable=false eventPhase=2]
Les 2 définitions d'objets ci-dessus ont un scope "singleton", les objets définis dans la fabrique sont automatiquement instanciés, initialisés et placés dans le cache de la fabrique IoC. Le cablage entre l'objet écouteur "listener" et le diffuseur d'évènement "dispatcher" est donc automatique.

Dans la définition d'objet dans l'attribut "listener" nous pouvons observer un objet générique qui contient les attributs suivants :

  • dispatcher (obligatoire) : Correspond à l'identifiant d'une définition d'objet enregistrée dans le conteneur. Cet attribut fonctionne comme l'attribut "ref" défini dans les attributs "arguments" et "properties" vus plus haut.
  • type (obligatoire) : Correspond au type de l'évènement utilisé pour abonner l'écouteur sur le diffuseur d'évènement.
  • method : Correspond au nom de la méthode qui va servir d'écouteur. (obligatoire si l'objet n'implémente pas l'interface vegas.events.EventListener)
  • useCapture : Permet de définir si l'écouteur sera notifié par un évènement pendant la phase de capture lors d'une propagation évènementielle lancée par un diffuseur d'évènement dans l'application. (optionnel)
  • priority : Permet de définir l'ordre de priorité de l'écouteur dans la collection de diffusion définie par le diffuseur d'évènement pour un type d'évènement donné. (optionnel)
  • useWeakReference : Active le mode useWeakReference de la méthode addEventListener du diffuseur d'évènement. (optionnel)
L'exemple précédent pourrait être défini en ActionScript avec le code suivant :

dispatcher.addEventListener("change", listener.change , false, 0 , true ) ;
Le câblage en couche des objets renvoyés par la fabrique IoC permet un contrôle complet sur tous les arguments de la méthode addEventListener() définie dans l'interface flash.events.IEventDispatcher.

Voyons maintenant une spécificité au niveau des objets génériques définis dans l'attribut "listeners" d'une définition d'objet. Nous avons pu voir précédemment que, de façon générale, il faudra définir une référence vers une autre définition d'objet dans le conteneur IoC et ensuite attacher sur le diffuseur d'évènements une méthode appartenant à l'objet écouteur pour un type d'évènement donné. Pour cibler convenablement la méthode désirée nous utilisons l'attribut "method" qui représente le nom de la méthode qui recevra les évènements (à condition que l'objet écouteur définisse bien une méthode du même nom).

Cette fonctionnalité est prioritaire. Dès que l'attribut "method" est défini dans une définition d'un écouteur alors c'est cette méthode qui sera utilisée.

Maintenant, il est possible d'aller un peu plus loin en oubliant délibérément de déclarer l'attribut "method" dans un objet générique défini dans l'attribut "listeners". Dans ce cas, il faudra absolument que l'objet généré par la définition d'objet implémente l'interface vegas.events.EventListener qui lui impose l'existence d'une méthode handleEvent(e:Event) comme défini dans le DOM 2/3 du modèle évènementiel du W3C. Cette fonction servira automatiquement de fonction écouteur dans l'objet de la définition d'objet courante.

Pour illustrer cette spécificité, je reprends l'exemple précédent en redéfinissant la classe test.Listener qui implémente maintenant l'interface vegas.events.EventListener.

package test
{
 
    import flash.events.Event;
 
    import vegas.events.EventListener ;
 
    public class Listener implements EventListener
    {
 
        public function Listener()
        {
            //
        }
 
        public function handleEvent( e:Event ):void
        {
            trace( this + " handleEvent" + e ) ;
        }
 
    }
}
Voyons maintenant comment utiliser très simplement cette classe dans une définition d'objet avec l'attribut "listeners" :

var context:Object =
[
    {
        id        : "dispatcher" ,
        type      : "flash.events.EventDispatcher" ,
        singleton : true
    }
    ,    
    {
        id        : "listener"   ,
        type      : "test.Listener" ,
        singleton : true ,
        listeners :
        [
            { dispatcher:"dispatcher" , type:"change"  }  
        ]
    }    
] ;
 
var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
factory.create( context ) ;
 
var dispatcher:EventDispatcher = factory.getObject("dispatcher") ;
 
dispatcher.dispatchEvent( new Event( "change" ) ) ; 
// [object Listener] handleEvent [Event type="change" bubbles=false cancelable=false eventPhase=2]
Plus besoin de déclarer l'attribut "method" dans ce cas de figure, la méthode handleEvent est utilisée automatiquement pour intercepter les évènements.

Il ne faut pas oublier qu'il est toujours possible de cibler avec l'attribut "method" une autre méthode définie dans la classe qui prendra la place de la méthode handleEvent() comme fonction écouteur.

Remarque :

Les objets génériques définis dans la collection (Array) de l'attribut "listeners" sont filtrés dans la fabrique et sont transformés en objet de type andromeda.ioc.core.ObjectListener. Cette classe contient une méthode statique "create()" qui prend en argument un Array d'objets génériques et qui renvoie un nouveau Array d'objets de type ObjectListener.
Exemple

import andromeda.ioc.core.ObjectListener ;
 
var init:Array =
[
    { dispatcher:"dispatcher1" , type:"click"  , method:"handleEvent" } ,
    { dispatcher:"dispatcher2" , type:"change" , method:"handleEvent" }
] ;
 
var listeners:Array = ObjectListener.create( init ) ;
 
trace( "listeners[0] : " + listeners[0] ) ;
 
// listeners[0] : [ObjectListener dispatcher:"dispatcher1" type:"click" method:"handleEvent"]
 
trace( "listeners[1] : " + listeners[1] ) ;
 
// listeners[1] : [ObjectListener dispatcher:"dispatcher2" type:"change" method:"handleEvent"]
	
 

Valid XHTML 1.0 TransitionalValid CSS!

Copyright © 2009 test. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.