2-1 - L'interface andromeda.util.visitor.IVisitable des "visiteurs".
package andromeda.util.visitor
    {
 
        /**
         * The basic IVisitable interface.
         * @author eKameleon
         */
        public interface IVisitable
        {
 
            /**
             * Accept the IVisitor object
             */
            function accept( visitor:IVisitor ):void ;
 
        }
    }
2-2 - L'interface andromeda.util.visitor.IVisitor des objets qui vont être "visités".
package andromeda.util.visitor
    {
 
        /**
         * The basic IVisitor interface.
         * To implements the Visitor pattern you can creates a concrete Visitor class who implements this interface.
         * @author eKameleon
         */
        public interface IVisitor
        {
 
            /**
             * Visit the IVisitable object.
             */
            function visit( o:IVisitable ):void ;
 
        }
    }
2-3 - La classe abstraite AbstractVisitable

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

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

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

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

1 - Description et architecture

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

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

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

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

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

La structure de ce petit tutoriel reste simple et classique :

1-1 - Répertoire contenant le fichier .fla (pour Flash CS3)
  • visitor/bin/visitor.fla : répertoire contenant les sources AS3 du tutoriel.
1-2 - Répertoires contenant le fichier .swf et l'image bitmap externe :
  • visitor/deploy/visitor.swf : fichier de production de l'exemple.
  • visitor/deploy/library/picture1.jpg : image bitmap chargée par le fichier visitor.swf.
1-3 - Répertoire de base des sources du projet (classpath) :
  • visitor/src/ : répertoire contenant les sources AS3 du tutoriel.
  • visitor/src/Visitor.as : Classe principale de l'application.

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

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

1-4 - Répertoire contenant les classes qui permettent de gérer les vues de l'application
  • visitor/src/display/
  • visitor/src/display/PictureDisplay.as : classe principale de la vue de l'application (conteneur graphique d'une image bitmap).
  • visitor/src/display/UIList.as : énumération statique de toutes les vues de l'applications (identifiant unique pour chaque vue importante).
1-5 - Répertoire contenant les IVisitors de la classe PictureDisplay
  • visitor/src/visitors/ClearVisitor.as : Ce IVisitor vide l'affichage du conteneur (faire disparaitre l'image uniquement)
  • visitor/src/visitors/HideVisitor.as : Ce IVisitor cache complètement le conteneur.
  • visitor/src/visitors/LoaderVisitor.as : Ce IVisitor contient les instructions pour charger une image bitmap externe dans le conteneur.
  • visitor/src/visitors/ShowVisitor.as : Ce IVisitor affiche complètement le conteneur.

2 - Création de la classe PictureDisplay

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

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

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

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

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

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

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

3 - Création des visitors de la classe PictureDisplay

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

3-1 - La classe ClearVisitor

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

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

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

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

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

3-2 - La classe HideVisitor

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

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

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

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

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

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

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

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

3-3 La classe ShowVisitor

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

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

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

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

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

3-4 - La classe LoaderVisitor

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

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

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

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

4 - Code principal de l'application

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

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

Dans cette classe le code est très simple :

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

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

Conclusion

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

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

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

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

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

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

En attendant bonne lecture :)

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