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


IV. Les différentes stratégies utilisées par la fabrique pour créer un objet avec une définition d'objet.
Introduction
IV-B. Utilisation de l'attribut "factoryMethod"
IV-C. Utilisation de l'attribut "factoryProperty"
IV-D. Utilisation de l'attribut "factoryReference"
IV-E. Utilisation de l'attribut "factoryValue"
IV-F. Utilisation de l'attribut "staticFactoryMethod"
IV-G. Utilisation de l'attribut "staticFactoryProperty"


IV. Les différentes stratégies utilisées par la fabrique pour créer un objet avec une définition d'objet.


Introduction

Dans les définitions d'objet il est possible d'utiliser des attributs un peu spéciaux, les "factory attributes".

Ces attributs permettent de définir une stratégie spéciale pour créer un objet en utilisant une technique différente de celle utilisée de façon classique dans la fabrique.

Nous pouvons résumer la stratégie utilisée par défaut dans une définition d'objet avec l'exemple suivant :

var objects:Array =
[
    { id : "my_object" , type : "Object" }
] ;
Pour récupérer une instance de l'objet défini dans la définition d'objet précédente, il suffit d'utiliser la méthode getObject() :

var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
factory.create( objects ) ;
 
var o:Object = factory.getObject( "my_object" ) ;
Dans la pratique, le code ci-dessus est très simple et équivalent au code suivant :

var o:Object = new Object() ;
Le conteneur IoC utilise donc dans la méthode getObject() tout simplement le mot clé "new" pour créer des instances ou des singletons avec des définitions d'objet classiques.

Il est donc possible d'aller plus loin en utilisant d'autres stratégies pour créer les objets avec une définition d'objet. Elles permettent par exemple d'utiliser dans une définition d'objet un objet déjà existant dans l'application ou même dans la fabrique, ou d'utiliser des outils externes pour créer, filtrer, référencer le nouvel objet.

Ces attributs sont basés sur un moteur défini par des classes situées dans le package andromeda.ioc.factory.strategy. Toutes les classes de ce package implémentent l'interface IObjectFactoryStrategy. Cette interface ne contient pas de méthode en particulier mais permet avant tout de regrouper certaines classes qui seront utilisées dans la fabrique pour créer correctement les objets et leurs dépendances.

Regardons de plus prêt une liste des attributs que nous pouvons utiliser dans une définition d'objet pour modifier la stratégie utilisée pour créer ou cibler un objet, chacun de ces attributs correspond à une classe spécifique et surtout à une stratégie de création ou récupération de l'objet différente :

L'attribut factoryMethod

Basé sur la classe ObjectFactoryMethod, cet attribut permet de créer un objet en utilisant la méthode d'une référence définie dans une autre définition d'objet.

L'attribut factoryProperty

Basé sur la classe ObjectFactoryProperty, cet attribut permet de définir un objet en utilisant la valeur d'un simple attribut défini dans une référence d'une autre définition d'objet.

L'attribut factoryReference

Basé sur la classe ObjectFactoryReference, cet attribut permet de cibler tout simplement une référence crée via une autre définition d'objet.

L'attribut factoryValue

Basé sur la classe ObjectFactoryValue, cet attribut permet d'utiliser n'importe quelle valeur définie directement dans le contexte de configuration de la fabrique IoC.

L'attribut staticFactoryMethod

Basé sur la classe ObjectStaticFactoryMethod, cet attribut permet de créer un objet dans la fabrique en utilisant une méthode statique définie dans une classe de l'application.

L'attribut staticFactoryProperty

Basé sur la classe ObjectStaticFactoryProperty, cet attribut permet de créer un objet dans la fabrique en utilisant la valeur ou référence d'un attribut statique ou d'une constante d'une classe définie dans l'application.

warning Il est important d'utiliser un et un seul de ces attributs pour une même définition d'objet et de ne surtout pas essayer d'utiliser plusieurs attributs en même temps.

IV-B. Utilisation de l'attribut "factoryMethod"

Cet attribut définit une stratégie qui permet :

  • de récupérer la référence d'un objet définie dans le conteneur IoC
  • puis d'invoquer une méthode sur cette référence pour fabriquer et retourner l'objet de la définition d'objet courante.
Pour illustrer l'utilisation de cet attribut nous commençons par définir une simple classe test.User :

package test
{
 
    public class User
    {
 
        public function User( name:String ) 
        {
            this.name = name ;
        }
 
        public var name:String ;
 
        public override function toString():String
        {
            return "[User" + ( name != null ? ( " " + name ) : "" ) + "]" ;    
        }
 
    }
 
}
Ensuite nous allons définir une classe qui permet de créer des instances de type test.User en se servant d'un filtrage via une liste noire (black list).

package test.factory
{
 
    /**
     * This factory creates User instances but use a filter with a black list array.
     * @author eKameleon
     */
    public class UserFilterFactory
    {
 
        /**
         * Creates a new UserFilterFactory instance.
         * @param blackList The optional array of string pseudo to banish in this factory.
         */
        public function UserFilterFactory( blackList:Array )
        {
            this.blackList = [] ;
        }
 
        /**
         * The blackList of this factory.
         */
        public var blackList:Array ;
 
        /**
         * Creates a new User instance with the specified pseudo.
         * @param pseudo The pseudo String representation of the new User.
         */
        public function build( pseudo:String ):User
        {
            if ( blackList.indexOf( pseudo ) > -1 )
            {
                 throw new Error(this + " build failed, the pseudo '" + pseudo + "' is register in the black list of the factory." ) ;
            }
            return new User( pseudo ) ;
        }
 
   }
 
}
Voyons maintenant comment utiliser l'attribut "factoryMethod" dans une définition d'objet avec les classes précédentes.

{  
    id         : "user_factory"     ,
    type       : "test.UserFilterFactory" ,
    arguments  :
    [
        { value : [ "lunas" , "pegas" ] } 
    ]
}
,
{  
    id            : "my_user"   ,
    type          : "test.User" ,
    factoryMethod : { factory:"user_factory" , name:"build" , arguments: [ { value : "vegas" } ]  }
}
,
{  
    id            : "your_user"   ,
    type          : "test.User" ,
    factoryMethod : { factory:"user_factory" , name:"build" , arguments: [ { value : "pegas" } ]  }
}
L'attribut est défini par un objet générique ayant les attributs suivants :

  • factory : Une chaine de caractère qui représente l'identifiant d'une référence d'objet défini dans le conteneur IoC.
  • name : Le nom de la méthode invoquée sur cette référence.
  • arguments : Cet attribut optionnel représente un Array de tous les arguments que l'on souhaite passer dans la fonction (voir chapitre B.1-3 L'attribut "arguments").
Finissons cette démonstration avec un petit test ActionScript qui illustrera l'utilisation des définitions d'objets définies ci-dessus :

import andromeda.ioc.factory.ECMAObjectFactory ;
 
import test.User ;
import test.factory.UserFilterFactory ;
 
var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
trace ( factory.getObject( "my_user" ) ) ; 
//output: [User vegas]
 
trace ( factory.getObject( "your_user" ) ) ; 
//output: [UserFilterFactory] build failed, the pseudo 'pegas' is register in the black list of the factory.

IV-C. Utilisation de l'attribut "factoryProperty"

Cet attribut définit une stratégie qui permet de :

  • récupérer la référence d'un objet défini dans une définition d'objet du conteneur IoC
  • puis de cibler une valeur ou une référence d'objet définie dans un des attribut de l'objet défini dans la fabrique.
L'attribut "factoryProperty" change donc la stratégie de création naturelle d'une définition d'objet en lui permettant de cibler rapidement une propriété d'un objet défini ailleurs dans la fabrique IoC.

Pour illustrer l'utilisation de cet attribut nous pouvons définir une classe test.User avec une simple attribut publique "name" :

package test
{
 
    public class User
    {
 
        public function User( name:String ) 
        {
            this.name = name ;
        }
 
        public var name:String ;
 
        public override function toString():String
        {
            return "[User" + ( name != null ? ( " " + name ) : "" ) + "]" ;    
        }
 
    }
 
}
Voyons maintenant comment utiliser l'attribut "factoryProperty" dans une définition d'objet en cherchant à cibler l'attribut name d'un objet de type test.User défini dans le conteneur IoC.

{  
    id        : "my_user"   ,
    type      : "test.User" ,
    arguments : [ { value:  "JohnDoe" } ]
}
,
{  
    id              : "my_name"   ,
    type            : "String" ,
    factoryProperty : { factory:"my_user" , name:"name" } 
}
L'attribut est défini par un objet générique ayant les attributs suivants :

  • factory : Une chaine de caractère qui représente l'identifiant d'un objet défini dans le conteneur IoC.
  • name : Le nom de la propriété que l'on souhaite cibler pour définir la valeur ou référence de l'objet défini dans la fabrique.
Finissons ce petit exemple avec un petit script ActionScript :

var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
var name:String = factory.getObject("my_name") as String ;
 
trace( name ) ; // JohnDoe
Dans la définition d'objet nous avons défini un objet de type String qui prend sa valeur dans la définition d'objet "my_user" en ciblant la propriété "name" de l'instance crée via la fabrique.

A noter qu'il ne faut pas oublier que cette stratégie permet de cibler aussi bien des attributs que des méthodes (objet de type Function) dans une instance sans les invoquer. J'utilise beaucoup cette stratégie qui permet de créer une copie par valeur d'une fonction ou méthode, par exemple dans un cas de figure de câblage évènementiel avec un objet de type flash.events.IEventDispatcher qui cherche à abonner une fonction écouteur avec sa méthode addEventListener :

{  
    id        : "my_listener"     , 
    singleton : true              ,
    type      : "test.MyListener" 
}
,
{
    id              : "my_listener_handler" ,
    type            : "Function" ,
    factoryProperty : { factory:"my_listener" , name:"handleEvent" } 
}
,
{  
    id         : "my_dispatcher"   ,
    type       : "flash.events.EventDispatcher" ,
    properties :
    [
       { name:"addEventListener" , arguments:[ { value:"change" } , { ref:"my_listener_handler" } ] }
    ]
}
Dans l'exemple précédent la classe test.MyListener implémente l'interface vegas.events.EventListener qui impose la méthode handleEvent(e:Event) dans une classe qui l'implémente. La définition d'objet "my_listener_handler" permet de récupérer une copie par valeur de la fonction qui doit être enregistrée comme écouteur au niveau de la définition d'objet "my_dispatcher" et de sa méthode addEventListener.

Remarque : Il est possible de simplifier l'exemple précédent en utilisant dans l'attribut "ref" du second argument de la méthode addEventListener avec la fonctionnalité avancée d'évaluation d'une référence avec la notation pointée (voir chapitre B-1-3-2 ) :

{  
    id        : "listener"     , 
    singleton : true              ,
    type      : "test.MyListener" 
}
,
{  
    id         : "dispatcher"   ,
    type       : "flash.events.EventDispatcher" ,
    properties :
    [
       { name:"addEventListener" , arguments:[ { value:"change" } , { ref:"listener.handleEvent" } ] }
    ]
}
Il faudra donc bien réfléchir avant d'utiliser l'attribut factoryProperty et bien cerner si il est indispensable de l'utiliser ou non.

A noter que l'exemple précédent peut être modifié également en utilisant l'attribut "listeners" ( chapitre B.1-7 ) qui inverse la dépendance entre l'objet qui diffuse les évènements et celui qui les écoute. Malgré tout je trouve très intéressant d'étudier les différentes solutions offertes par la fabrique IoC pour câbler convenablement les objets entre eux dans le contexte d'une programmation orientée objet basée sur le modèle évènementiel AS3 (basé DOM2/3 du W3C).


IV-D. Utilisation de l'attribut "factoryReference"

L'attribut "factoryReference" définit une stratégie qui permet de créer un objet en ciblant une autre définition d'objet enregistrée dans le conteneur IoC. Cet attribut a pour valeur une simple chaine de caractère (String) qui correspond à l'identifiant d'une autre définition d'objet dans le conteneur IoC, celle-ci servira de base pour créer l'objet de la définition courante.
Exemple

{  
    id            : "user"       ,
    type          : "test.User"  ,
    properties    : 
    [
        { name : "city" , value : "Marseille" }
    ]
}
,
{  
    id               : "my_user"   ,
    type             : "test.User" ,
    factoryReference : "user"      ,
    singleton        : true        ,
    lazyInit         : true        ,
    properties       :
    [ 
        { name : "name"      , value : "Doe"  } ,
        { name : "firstname" , value : "John" }
    ]
}
Dans l'exemple précédent nous observons que la définition d'objet portant l'identifiant "user" sert de base pour créer un objet singleton dans la définition d'objet "my_user". Voyons maintenant dans un code ActionScript comment utiliser ces définitions d'objet :

import test.User ;
 
var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
var user:User   = factory.getObject( "my_user" ) as User   ;
var city:String = factory.getObject( "my_city" ) as String ;
 
trace( "user.name : " + user.firstname ) ; // John
trace( "user.city : " + user.city ) ; // Marseille
Cette stratégie peut être très utile pour définir une sorte de template plus ou moins avancé qui permettra de créer plusieurs instances dans la fabrique basées sur ce template mais ayant ensuite chacune leurs particularités et différences.

A noter que la définition d'objet "my_city" définie plus haut met en avant l'utilisation de la classe andromeda.ioc.evaluators.ReferenceEvaluator. Cette classe est utilisée dans toutes les définitions d'objet au moment de créer ou d'initialiser un objet au niveau des attributs "ref", "factoryReference", "dispatcher" (dans les attributs "listeners") et elle permet d'évaluer une expression (String) en retournant en priorité la référence d'un objet défini dans la fabrique IoC avec un identifant correspondant à l'expression évaluée. Il est possible d'aller un peu plus loin en utilisant une syntaxe pointée pour récupérer en profondeur dans l'objet la valeur d'un de ses attributs (plus d'explication dans le chapitre sur l'attribut "evaluators") :

{  
    id            : "user"       ,
    type          : "test.User"  ,
    properties    : 
    [
        { name : "city" , value : "Marseille" }
    ]
}
,
{
    id               : "my_city"    ,
    type             : "String"     ,
    factoryReference : "user.city"
}
Dans l'exemple ci-dessus, l'attribut factoryReference permet de cibler l'attribut "city" de la référence "user" définie dans le conteneur IoC :

import test.User ;
 
var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
var city:String = factory.getObject("my_city") as String ;
 
trace( "city      : " + city      ) ; // Marseille

IV-E. Utilisation de l'attribut "factoryValue"

Cette stratégie est la plus simple de toute car elle permet de définir directement une valeur simple ou "custom" dans la définition d'objet. Celle-ci servira de valeur ou référence directement renvoyée par la méthode getObject() du conteneur.

{  
    id           : "my_string"    ,
    type         : "String"       ,
    factoryValue : "hello world"
}
,
{  
    id           : "my_date" ,
    type         : "Date"    ,
    factoryValue : new Date(1977,2,22)
}
,
{  
    id           : "my_object" ,
    type         : "Object"    ,
    properties   : 
    [
        { name : "message" , ref:"my_string" } ,
        { name : "date"    , ref:"my_date"   }
    ]
}
Les définitions d'objet ci-dessus servent en quelque sorte de variables dans la fabrique si nous les utilisons avec un passage par référence par la suite. C'est surtout avec l'écriture au format eden des fichiers de configuration externes du conteneur que cet attribut prend tout son intérêt en déclarant un objet typé (custom ou non) en profitant pleinement des possibilités étendues du parseur eden.


IV-F. Utilisation de l'attribut "staticFactoryMethod"

Pour illustrer l'utilisation de cet attribut nous allons commencer par déclarer une petite classe simplifiée test.User :

package test
{
 
    public class User
    {
 
        public function User( name:String ) 
        {
            this.name = name ;
        }
 
        public var name:String ;
 
    }
 
}
Nous définissons maintenant une fabrique dans notre application avec une classe qui contiendra une méthode statique :

package test
{
 
    public class UserFactory 
    {
 
        public static function create( name:String ):User
        {
            return new User(name) ;
        }
 
    }
}
Voyons pour finir comment utiliser cette classe et sa méthode statique dans la définition d'un objet de notre conteneur IoC.

{  
    id                  : "my_user"   ,
    type                : "test.User" ,
    staticFactoryMethod :
    {
        type      : "test.UserFactory" ,
        name      : "create"           ,
        arguments : [ { value : "ekameleon" } ]
    }
}
A noter, que dans VEGAS et ses extensions de nombreuses classes utilisent des méthodes statiques pour générer des singletons ou des références globales. Nous pouvons donc ajouter un autre exemple simple avec la classe vegas.events.FrontController qui peut gérer des références globales via une gestion de "singletonsmulti-channels" avec sa méthode getInstance(channel:String = null) (fonctionne comme la classe ECMAObjectFactory, voir chapitre A.3-6).
Exemple

{  
    id                  : "front_controller"   ,
    type                : "vegas.events.FrontController" ,
    singleton           : true ,
    staticFactoryMethod : 
    { 
        type      : "vegas.events.FrontController" , 
        name      : "getInstance" , 
        arguments : [ { value : "my_channel" } ]  
    }
}
Cette stratégie est donc fortement conseillée avec de nombreuses classes implémentant des méthodes statiques dans VEGAS et ses extensions ou dans vos propres classes.


IV-G. Utilisation de l'attribut "staticFactoryProperty"

Cet attribut permet de récupérer une référence ou une valeur située dans un attribut statique ou dans une constante définie dans une classe de l'application.

Par exemple nous pouvons créer rapidement une classe d'énumération statique test.Civility qui contiendra un ensemble de constantes de type String :

package test
{
 
    /**
     * The civility static enumeration list.
     */
    public class Civility
    {
 
        /**
         * Determinates the "man" civility.
         */
        public static const MAN:String = "man" ;
 
        /**
         * Determinates the "woman" civility.
         */
        public static const WOMAN:String = "woman" ;
 
    }
}
Dans une définition d'objet de la configuration du conteneur IoC nous pouvons alors utiliser la stratégie définie par l'attribut "staticFactoryProperty" pour cibler une des constantes de la classe ci-dessus :

objects : 
{
    {   
        id                    : "man"    , 
        type                  : "String" , 
        staticFactoryProperty : { type:"test.Civility" , name:"MAN" }
    }
}
L'attribut staticFactoryProperty a pour valeur un simple objet générique avec les 2 attributs :

  • type : le nom complet de la classe que l'on souhaite cible.
  • name : le nom de la propriété statique ou de la constante définie dans la classe.

var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
var civility:String = factory.getObject("man") ;
 
trace( civility ) ; // man
L'exemple précédent est vraiment basique, mais il faut bien comprendre que la statégie de l'attribut "staticFactoryProperty" permet d'aller plus loin. Elle permet par exemple de récupérer certains objets définis dans l'application et de les mettre à disposition simplement dans le conteneur IoC en appliquant au passage certaines modifications et initialisations avant utilisation.

Par exemple dans une application MXML basée sur le Flex SDK nous pouvons retrouver facilement dans une fabrique IoC la référence singleton de l'application principale en ciblant l'attribut statique http://livedocs.adobe.com/flex/201/html/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Book_Parts&file=app_container_064_10.html :

objects :
{
    {
        id   : "myPanel" ,
        type : "mx.controls.Panel"
    }
    ,
    {  
        id                    : "application"    ,
        type                  : "mx.core.Application" ,
        singleton             : true ,
        lazyInit              : true ,
        staticFactoryProperty : { type:"mx.core.Application" , name:"application" } ,
        properties            :
        [
            { name:"addChild" , arguments:[ { ref:"myPanel" } ] }
        ]
    }
}
La définition d'objet précédente nous donne donc l'accès dans le conteneur IoC vers une référence de l'application principale de l'application.

Cette définition d'objet est singleton et utilise l'attribut lazyInit. Quand nous appellerons pour la première fois la méthode getObject() du conteneur IoC avec l'identifiant "application", la méthode addChild sera automatiquement invoquée sur la scène principale de l'application pour attacher des composants MXML (customs ou non).

 

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.