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


III. Cycle de vie d'un objet configuré avec une définition d'objet.
III-A. Les attributs "init" et "destroy".
III-B. L'attribut "identify".
III-C. L'attribut "lock".
III-D. Les attributs "dependsOn" et "generates".


III. Cycle de vie d'un objet configuré avec une définition d'objet.


III-A. Les attributs "init" et "destroy".

Il est possible de définir dans une définition d'objet une méthode qui sera invoquée à la fin de l'initialisation d'un objet une fois généré via le conteneur IoC. Cette méthode devra être définie dans la classe de l'objet renvoyé et sera définie sans paramètre. Cette méthode sera invoquée à la fin de l'initialisation de l'objet dans la méthode getObject() du conteneur.

Pour définir cette méthode il suffira d'utiliser l'attribut "init" dans une définition d'objet, cet attribut est de type String et prend pour valeur le nom de la méthode que l'on souhaite invoquer.

A noter qu'il est également possible de définir une méthode qui sera invoquée lorsque un objet renvoyé par la fabrique sera détruit par cette même fabrique. Il faut dans ce cas que l'objet ait un scope "singleton" et soit placé en mémoire dans la fabrique au moment de sa création avec la méthode getObject(). Pour définir le nom de la méthode utilisée à ce moment là nous utiliserons l'attribut "destroy" dans les définitions d'objets.
Exemple

{  
   id         : "user" ,
   type       : "test.User" ,
   singleton  : true ,
   destroy    : "destroy" ,
   init       : "initialize"
}
Dans l'exemple précédent la méthode initialize() du singleton de type test.User sera invoquée au moment de sa création avec la méthode factory.getObject("user") du conteneur.

L'instance de type test.User est définie avec un scope "singleton" dans la fabrique et il sera donc possible en cas de nécessité de le supprimer dans le cache interne de la fabrique avec la méthode "removeSingleton(id)" :

ECMAObjectFactory.removeSingleton( "user" ) ; // the destroy method is invoked
La méthode removeSingleton() se charge d'invoquer la méthode définie avec l'attribut "destroy" avant de la supprimer la référence dans le cache de la fabrique.

info Il est possible de généraliser dans toutes les définitions d'objets d'une fabrique l'appel automatique des méthodes d'initialisation et de destruction (si elles existent dans la classe de l'objet défini) en configurant la fabrique convenablement. Vous pouvez consulter à ce sujet le chapitre C-1-2 sur les attributs de configuration "defaultInitMethod" et "defaultDestroyMethod".

III-B. L'attribut "identify".

Cet attribut de type Boolean doit être utilisé uniquement avec une définition d'objet avec un scope "singleton" et si le type de l'objet défini implémente l'interface vegas.core.Identifiable.

L'interface vegas.core.Identifiable définie simplement un objet avec un identifiant unique "id". Cette interface est implémentée par de nombreux objets dans les différentes classes de VEGAS et ses extensions.

Quand l'attribut identify est vrai (true), le nouveau objet Identifiable créé par la fabrique est automatiquement identifié avec l'identifiant de sa définition d'objet.
Exemple

objects =
[        
    {   
        id          : "vo_01"  ,
        type        : "andromeda.vo.NetServerInfoVO" ,
        singleton   : true ,
        identify    : true ,
        arguments   : 
        [ 
            value : 
            { 
                application : "local" ,
                level       : "error" ,
                code        : "application_error" ,
                description : "An error is invoqued in the application" ,
                line        : 3 ,
                methodName  : "noMethod" ,
                serviceName : "noService" 
            } 
        ]
    }    
] ;
Dans cet exemple la classe andromeda.vo.NetServerInfoVO implémente l'interface vegas.core.Identifiable.

Une fois la fabrique initialisée avec cette définition d'objet, nous pouvons tester celle ci avec le code suivant :

var info:NetServerInfoVO = factory.getObject( "vo_01" ) ;
 
// The id of the vo is auto initialized if the identify property of the ObjectDefinition of this object is true.
 
trace( "info.id : " + info.id ) ; // info.id : vo_01
La valeur de l'attribut "id" de l'instance de type NetServerInfoVO est automatiquement définie avec la valeur "vo_01". L'identifiant dans la fabrique de la définition de l'objet est bien le même que celle du singleton récupéré dans la fabrique.

info Cet attribut est prioritaire sur un attribut du même nom défini dans la configuration de la fabrique, la référence de type ObjectConfig dans la classe ObjectFactory contient elle aussi une propriété identify booléenne qui permet de généraliser l'action d'initialisation automatique des singletons identifiables définis dans la fabrique (voir chapitre C-1-3)

III-C. L'attribut "lock".

Cet attribut de type Boolean doit être utilisé uniquement avec une définition d'objet qui permet de créer un objet qui implémente l'interface vegas.core.ILockable.

Cet attribut peut prendre les valeurs suivantes :

  • null : L'objet défini dans le conteneur IoC n'est pas protégé par défaut au moment de son initialisation sauf si la propriété "lock" de la configuration de la fabrique vaut true.
  • true : L'objet défini dans la fabrique est protégé durant son initialisation (cet attribut est prioritaire sur l'attribut "lock" défini dans la configuration du conteneur IoC).
  • false : L'objet défini dans la fabrique ne sera pas protégé durant son initialisation même si la propriété "lock" de la configuration du conteneur vaut true.
De façon général l'interface ILockable définie dans une classe qui l'implémente les trois méthodes lock(), unlock() et isLocked().

Cette interface minimaliste peut être implémentée par toute classe qui nécessite dans son cycle de vie de nombreuses mises à jours ou de nombreuses actions répétitives qui pourraient nuire en diminuant les performances de l'ensemble de l'application.

Prenons par exemple une classe d'un composant qui possède plusieurs propriétés virtuelles (get/set). Ce composant utilise des propriétés pour mettre à jour ses couleurs, la taille de son texte, etc. Imaginons ensuite que toutes ses propriétés une fois modifiées exécutent à chaque fois une méthode qui permet de mettre à jour la vue du composant.

Si nous cherchons à créer plusieurs instances de cette même classe et au même moment à initialiser plusieurs propriétés de ces instances, nous pouvons rapidement avoir une surchage du CPU avec une relance inutile de la méthode d'initialisation de tous les composants.

D'où l'intérêt dans ce cas de figure d'implémenter l'interface ILockable et de définir les 3 méthodes de cette interface pour ensuite créer des sécurités dans le code et ainsi avoir la possibilité de contrôler plus finement les mises à jour de chaque composant le moment voulu.

Pour illustrer ce concept je vais créer une classe visuelle test.display.RectangleSprite qui permet de créer des symboles graphiques dynamiques rectangulaires. Cette classe implémente l'interface ILockable.

package test.display
{
    import flash.display.Sprite;
 
    import vegas.core.ILockable;   
 
    /**
     * This sprite draw a rectangle with a virtual width and height value.
     */
    public class RectangleSprite extends Sprite implements ILockable
    {
 
        /**
         * Creates a new RectangleSprite instance.
         */
        public function RectangleSprite()
        {
            super() ;
        }
 
        /**
         * The color of the rectangle shape.
         */
        public function get color():uint
        {
            return _color ; 
        }       
 
        /**
         * @private
         */
        public function set color( value:uint ):void
        {
            _color = value ;
            update() ;
        }        
 
        /**
         * Determinates the virtual height value of this sprite.
         */
        public function get h():Number
        {
            return _h ; 
        }
 
        /**
         * @private
         */
        public function set h( n:Number ):void
        {
            _h = isNaN(n) ? 0 : n ;
            update() ;
        }       
 
        /**
         * Determinates the virtual height value of this sprite.
         */
        public function get w():Number
        {
            return _w ; 
        }
 
        /**
         * @private
         */
        public function set w( n:Number ):void
        {
            _w = isNaN(n) ? 0 : n ;
            update() ;
        }           
 
        /**
         * Draw the display.   
         */
        public function draw():void
        {
            graphics.clear() ;
            graphics.beginFill( color , 1 ) ;
            graphics.drawRect( 0, 0, w, h ) ;
        }       
 
        /**
         * Returns true if the object is locked.
         * @return true if the object is locked.
         */
        public function isLocked():Boolean
        {
            return ___isLock___ == true ;
        }
 
        /**
         * Locks the object.
         */
        public function lock():void
        {
            ___isLock___ = true ;
        }
 
        /**
         * Unlocks the display.
         */
        public function unlock():void
        {
            ___isLock___ = false ;
        }
 
        /**
         * Update the display.
         */
        public function update():void
        {
            if ( isLocked() )
            {
                return ;
            }
            trace(this + " update") ;
            draw() ;
        }
 
        /**
         * @private
         */
        protected var _color:uint = 0xFF0000 ;       
 
        /**
         * @private
         */
        protected var _h:Number = 0 ;
 
        /**
         * @private
         */
        protected var _w:Number = 0 ;
 
        /**
         * The internal flag to indicates if the display is locked or not.
         * @private
         */
        private var ___isLock___:Boolean ;       
 
    }
}
La classe RectangleSprite possède les propriétés virtuelles color, h et w qui permettent de définir respectivement la couleur, la hauteur et la largeur du rectangle que l'on souhaite dessiner dynamiquement et afficher. Chaque appel d'une de ces propriétés virtuelles lance automatiquement la méthode update() pour mettre à jour la vue du Sprite avec la méthode draw() définie dans la classe.

La classe RectangleSprite implémente l'interface ILockable et son implémentation reste très simple avec un simple attribut booléen qui peut permuter entre une valeur true ou false avec les méthodes lock() et unlock(). Nous utilisons donc un point d'arrêt avec la méthode isLocked() dans la méthode update() de la classe :

/**
 * Update the display.
 */
public function update():void
{
   if ( isLocked() )
   {
       return ; // Point d'arrêt ici si isLocked() renvoi "true".
   }
   trace( this + " update" ) ;
   draw() ;
}
Ce point d'arrêt avec une instruction return sera invoquée si la méthode isLocked() renvoie true. Cette méthode très simple peut être déployée dans n'importe quelle classe, il suffira d'implémenter l'interface ILockable et de définir en gros ses méthodes comme dans la classe RectangleSprite ci-dessus.

A noter que dans VEGAS et toutes ses extensions de nombreuses classes implémentent déjà cette interface et il sera assez simple de s'inspirer de ces classes pour aller un peu plus loin dans vos propres applications.

Voyons maintenant un petit bout de code ActionScript pour illustrer l'utilisation de cette implémentation :

import test.display.RectangleSprite ;
 
var sprite:RectangleSprite = new RectangleSprite() ;
 
sprite.color = 0xFF00FF
sprite.w     = 200 ;
sprite.h     = 180 ;
 
sprite.x     = 25 ;
sprite.y     = 25 ;
 
addChild( sprite ) ;
Je copie le code précédent dans une petite animation Flash et je lance la compilation, j'obtiens un rectangle fushia sur la scène et dans le panneau de sortie les trace suivantes :

Je copie le code précédent dans une petite animation Flash et je lance la compilation, j'obtiens un rectangle fushia sur la scène et dans le panneau de sortie les trace suivantes :
La méthode update() est donc utilisée 3 fois de suite lors de l'initialisation de l'instance avec les propriétés w, h et color.

Dans un petit bout de code comme celui-ci, la notion d'optimisation n'est pas très importante, mais à plus grande échelle si nous cherchons à instancier plusieurs objets nous observerons un nombre incalculable d'appels inutiles de la méthode update().

Voyons maintenant comment utiliser l'interface ILockable :

import test.display.RectangleSprite ;
 
var sprite:RectangleSprite = new RectangleSprite() ;
 
sprite.lock() ;
 
sprite.color = 0xFF00FF
sprite.w     = 200 ;
sprite.h     = 180 ;
 
sprite.unlock() ;
 
sprite.update() ;
 
sprite.x     = 25 ;
sprite.y     = 25 ;
 
addChild( sprite ) ;
Nous obtenons dans le panneau de sortie un seul trace :

[object RectangleSprite] update
La méthode update() est donc solicitée qu'une seule fois après la méthode unlock(). En AS3, nous retrouvons cette implémentation (sans l'interface ILockable) dans certains objets comme la classe native flash.display.BitmapData.

Voyons maintenant la définition d'objet correspondante au code ActionScript précédent en utilisant l'attribut "lock" :

objects =
[
    {  
        id          : "my_rectangle"  ,
        type        : "test.display.RectangleSprite" ,
        lock        : true     , // can be 'true', 'false' or 'null'
        init        : "update" ,
        properties  :
        [
            { name : "color" , value : 0xFF00FF } ,
            { name : "w"     , value : 200      } ,
            { name : "h"     , value : 180      } ,
            { name : "x"     , value :  20      } ,
            { name : "y"     , value :  20      }                                
        ]
    }
] ;
Voyons comment utiliser cette définition d'objet dans un conteneur léger IoC :

import andromeda.ioc.factory.ECMAObjectFactory ;
 
import flash.display.StageAlign ;
import flash.display.StageScaleMode ;
 
import test.display.RectangleSprite ;
 
stage.align     = StageAlign.TOP_LEFT ;
stage.scaleMode = StageScaleMode.NO_SCALE ;
 
var objects:Array =
[
    {   
        id          : "my_rectangle"  ,
        type        : "test.display.RectangleSprite" ,
        lock        : true     ,
        init        : "update" ,
        properties  :
        [
            { name : "color" , value : 0xFF00FF } , // launch the update method
            { name : "w"     , value : 200      } , // launch the update method
            { name : "h"     , value : 180      } , // launch the update method
            { name : "x"     , value : 20       } ,
            { name : "y"     , value : 20       }                                 
        ]
    }
] ;
 
var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
// if true all ILockable object are locked during the initialization of the properties and methods of the object in the factory.
// factory.config.lock = true ;
 
factory.create( objects ) ;
 
var rec:RectangleSprite = factory.getObject("my_rectangle") ;
 
addChild( rec ) ;
La définition d'objet insérée dans la fabrique IoC permet de créer avec la méthode factory.getObject() une instance de la classe test.display.RectangleSprite, elle est attachée ensuite sur la scène principale avec la méthode addChild().

La méthode lock() est automatiquement utilisée sur l'instance pendant l'initialisation des propriétés déclarées dans la définition d'objet.

La méthode unlock() est invoquée ensuite juste avant l'exécution de la méthode update() définie comme méthode d'initialisation avec l'attribut "init" dans la définition d'objet.

A noter dans l'exemple précédent le petit commentaire :

// if true all ILockable object are locked during the initialization of the properties and methods of the object in the factory.
// factory.config.lock = true ;
Il est donc possible de définir de façon plus générale un attribut "lock" dans la configuration de la fabrique IoC pour que tous les objets ILockable instanciés dans le conteneur IoC soient protégées avec les méthodes lock() et unlock() au moment de leur initialisation. Il suffit pour cela de désactiver le commentaire de la ligne "factory.config.lock = true" pour tester cet attribut global de configuration de la fabrique (voir explication plus complète dans le chapitre C-1-3).


III-D. Les attributs "dependsOn" et "generates".

Les attributs "dependsOn" et "generates" permettent de lier une définition objet avec d'autres définitions d'objet enregistrées dans une même fabrique IoC.

Ces attributs sont définis par un objet de type Array qui contient un ensemble d'identifiants (String). Chaque identifiant correspond à une définition d'objet dans la fabrique IoC.

A chaque création d'un nouveau objet via la fabrique, la fabrique pourra initialiser et créer certaines de ses dépendances automatiquement.

Il n'est pas toujours possible d'initialiser les dépendances d'un objet en lui passant une référence de celle ci dans une de ses méthode, ou en lui passant en paramètre de son constructeur, etc. Il faudra donc utiliser les attributs "dependsOn" et "generates" pour créer certaines objets et assurer le bon fonctionnement de l'objet appelé avec la méthode getObject() dans la fabrique.

Les objets définis dans l'attribut "dependsOn" seront créés ou initialisés avant la phase d'initialisation du nouveau objet appelé par référence dans la fabrique ou directement dans le code de l'application. Nous utiliserons donc cet attribut pour créer des objets qui n'existent pas pour le moment dans l'application mais qui sont indispensables pour créer et initialiser correctement le nouveau objet. Nous pouvons donc dire que l'objet dépend d'autres objets et ses dépendances doivent absolument exister avant d'initialiser l'objet principal.

Les objets définis dans l'attribut "generates" seront créés ou initialisés après la phase d'initialisation du nouveau objet appelé dans la fabrique avec la méthode getObject(id). Nous pourrons dire que les objets définis dans cet attribut dépendent de l'objet crée avec la fabrique sous réserve qu'il soit totalement initialisé avant que le conteneur IoC les fabrique.
Exemple d'utilisation des attributs dependsOn et generates :

import andromeda.ioc.factory.ECMAObjectFactory ;
 
import flash.events.Event ;
 
var click:Function = function( e:Event ):void
{
    trace("click") ;
}
 
var context:Object =
[
     {
        id         : "my_button" ,
        type       : "MyButton"  , // a symbol in the library of the application
        singleton  : true        ,
        dependsOn  : [ "root_before" ] ,
        properties :
        [
            { name : "buttonMode" , value : true } ,
            { name : "x"          , value : 50   } ,
            { name : "y"          , value : 50   }
        ]
        ,
        generates  : [ "root_after"  ]
    }
    ,
    {
        id               : "root_before" ,
        type             : "flash.display.MovieClip" ,
        factoryReference : "#root" ,
        listeners        :
        [
            { dispatcher:"my_button" , type:"click" , method:"click" }
        ]
    }   
    ,
    {
        id               : "root_after" ,
        type             : "flash.display.MovieClip" ,
        factoryReference : "#root" ,
        properties       :
        [
            { name:"addChild" , arguments:[ { ref:"my_button" } ] }
        ]
    }   
] ;
 
var factory:ECMAObjectFactory = ECMAObjectFactory.getInstance() ;
 
factory.config.root = this ;
 
factory.create( context ) ;
info Sans notion de dépendance, cette fonctionnalité permet également de créer de nombreux objets à un moment précis dans l'application en utilisant pour cela un seul objet qui servira de déclencheur pour tous les autres. Cette fonctionnalité est utilisée souvant avec des objets singleton mais ce n'est pas obligatoire.
 

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.