I. Introduction▲
Cela fait quelque temps maintenant que je vous parle de VEGAS, mais il est vrai qu'il manquait des vrais tutoriels pour vous montrer comment utiliser mon framework…
Je trouve intéressante l'idée de progressivement vous amener à conceptualiser l'intégralité de vos applications avec lui. Je vais en profiter au passage pour mettre en avant certains outils précis en passant en revue quelques Design Patterns (FrontController, Observer, Singleton, Visitor…). Il est certain que, dans ma façon de travailler, certains vont peut-être reconnaitre des notions déjà vues ailleurs dans d'autres frameworks comme Pixlib par exemple… Pas de secret, les quelques projets que j'ai pu réaliser cette année avec Francis Bourre ont étaient décisifs dans l'orientation actuelle de mon travail au niveau de l'utilisation d'un FrontController pour globaliser les flux d'événements dans mes applications.
Je vais donc commencer ces tutoriels par la mise en avant de quelques Design Patterns que j'aime bien utiliser au quotidien dans mes applications ActionScript.
Voici donc un premier tutoriel basé sur le pattern Visitor. À mon avis, ce pattern n'est pas très connu dans le domaine de l'ActionScript, je ne l'ai jamais rencontré et j'ai eu un peu de mal à trouver une implémentation simple pour l'utiliser simplement en ActionScript… Donc j'espère que les experts en Design Patterns pourront me dire si ma vision de ce pattern ne leur parait pas trop éloignée de sa vraie nature. L'exemple présenté ici est bien entendu qu'un petit exercice sans prétention pour comprendre les concepts de base.
II. Généralité▲
Le Design Pattern Visitor permet simplement d'ajouter sur une classe ou instance des fonctionnalités non prévues au départ sans surcharger la structure de base de l'objet et en gardant si possible une certaine flexibilité et souplesse pour ajouter ou enlever rapidement d'autres fonctionnalités sans alourdir à nouveau l'implémentation définie au départ. En complément de mon explication, vous pouvez lire cet article à ce sujet.
Pour faire simple, le Pattern Visitor s'articule autour d'une classe qui pourra être « visitée » par d'autres classes via une méthode accept() (le nom de la fonction peut varier selon les implémentations du pattern). Cette méthode « accept » prend en paramètre un objet de type « visiteur » et va lancer automatiquement la méthode visit() du « visiteur ». Ainsi l'objet « visiteur » pourra controler convenablement l'objet « visité » en récupérant simplement la référence de cet objet.
Dans VEGAS, j'ai implémenté deux interfaces très simples pour structurer ce Pattern. Il est bien entendu possible de juste s'inspirer de ces deux interfaces pour réaliser d'autres conceptualisations selon des besoins précis. Mais dans mes travaux quotidiens, ces deux interfaces sont bien suffisantes.
Voyons de plus près, ces deux interfaces dans la librairie AS2 de VEGAS :
1 - vegas.util.visitor.IVisitable : l'interface des « visiteurs ».
import
vegas.
util.
visitor.
IVisitor;
/**
* The basic IVisitor interface.
* @author eKameleon
*/
interface
vegas.
util.
visitor.
IVisitable
{
/**
* Accept a IVisitor object
*/
function
accept
(
visitor:
IVisitor ) ;
}
2 - vegas.util.visitor.IVisitor : l'interface des objets qui vont être « visités ».
import
vegas.
util.
visitor.
IVisitable;
/**
* The basic IVisitor interface.
* To implements the Visitor pattern you can creates a concrete Visitor class who implements this interface.
* @author eKameleon
*/
interface
vegas.
util.
visitor.
IVisitor
{
/**
* Visit the IVisitable object.
*/
function
visit
(
o:
IVisitable ):
Void
;
}
Je conceptualise très simplement ce pattern pour le moment et pour cela j'utilise simplement une classe abstraite AbstractVisitable qui me permet rapidement d'implémenter ce Pattern. Il est bien entendu possible de juste s'inspirer de cette classe pour orienter différemment le DP selon vos besoins dans vos propres classes qui implémenteront simple l'interface IVisitable.
import
vegas.
core.
CoreObject;
import
vegas.
util.
visitor.
IVisitable;
import
vegas.
util.
visitor.
IVisitor;
/**
* 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
*/
class
vegas.
util.
visitor.
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.
*/
function
accept
(
visitor:
IVisitor )
{
visitor.
visit
(
this
) ;
}
}
Vous pouvez voir, comme je l'ai expliqué plus haut, qu'une 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.
III. Exemple d'utilisation du pattern▲
Pour illustrer simplement ce Design Pattern j'ai décidé de mettre en avant une classe Picture qui sera IVisitable et qui va me permettre de gérer la vue d'une image chargée dynamiquement dans un clip. L'exercice de ce tutoriel servira de bases pour tous les autres tutoriels qui vont arriver donc je vais essayer d'être précis dans mes explications.
Remarque : Vous pouvez retrouver les sources de cet exemple dans le subversion de VEGAS dans le répertoire AS2/trunk/bin/test/vegas/util/visitor/
Au niveau visuel l'exercice nécessite simplement un clip contenant une simple forme rectangulaire. J'ai choisi de créer un rectangle de 260 pixels par 260 (je me base avec cette taille sur les images qui seront chargées dans le clip via un clip « container » dynamique). Je pose au centre de la scène de mon application ce clip et je lui donne comme nom d'occurrence : picture_mc.
À mon avis à ce niveau-là pas trop de difficulté ;) Vous pouvez retrouver le fla principal de cet exercice avec le fichier Picture.fla dans le répertoire défini au-dessus.
Toutes les classes de l'exemple sont dans le répertoire test/ à la racine du répertoire contenant le fichier Picture.fla.
1 - Je commence simplement par la classe test.visitor.diaporama.Picture qui hérite de AbstractVisitable.
import
vegas.
util.
visitor.
AbstractVisitable;
import
vegas.
util.
visitor.
IVisitor;
/**
* The Picture class to display a picture in a Movieclip who contains a container movieclip inside.
* @author eKameleon
*/
class
test.
visitor.
diaporama.
Picture extends
AbstractVisitable
{
/**
* Creates a new Picture instance.
*/
public
function
Picture
(
name
:
String
,
target
:
MovieClip
,
url
:
String
,
hide
:
Boolean
)
{
this
.
name
=
name
;
this
.
url
=
url
;
view =
target
;
container =
view.
createEmptyMovieClip
(
"container"
,
1
) ;
container.
_x
=
margin ;
container.
_y
=
margin ;
setVisible
(
!
hide
) ;
}
/**
* The container reference in the picture display.
*/
public
var
container:
MovieClip
;
/**
* The margin in the picture to display the container.
*/
public
var
margin:
Number
=
10
;
/**
* The name of the picture.
*/
public
var
name
:
String
;
/**
* The url of the picture.
*/
public
var
url
:
String
;
/**
* The view of the picture.
*/
public
var
view:
MovieClip
;
/**
* Returns true if the picture is visible.
*/
public
function
isVisible
(
):
Boolean
{
return
view.
_visible
;
}
/**
* Sets the visible property of the picture.
*/
public
function
setVisible
(
b:
Boolean
):
Void
{
view.
_visible
=
b ;
}
/**
* Returns the string representation of this object.
* @return the string representation of this object.
*/
public
function
toString
(
):
String
{
return
"[Picture "
+
name
+
"]"
;
}
}
Cette classe est très simple, elle utilise un traitement par composition de la vue définie en paramètre de la fonction constructeur. Je vais détailler les points importants de cette classe :
Héritage de la classe AbstractVisitable qui assure l'implémentation de l'interface IVisitable
/**
* The Picture class to display a picture in a Movieclip who contains a container movieclip inside.
* @author eKameleon
*/
class
test.
visitor.
diaporama.
Picture extends
AbstractVisitable
Fonction constructeur de la classe qui prend en paramètre la cible du clip qui servira de conteneur pour l'image chargée dynamiquement :
/**
* Creates a new Picture instance.
*/
public
function
Picture
(
name
:
String
,
target
:
MovieClip
,
url
:
String
,
hide
:
Boolean
)
{
this
.
name
=
name
;
this
.
url
=
url
;
view =
target
;
container =
view.
createEmptyMovieClip
(
"container"
,
1
) ;
container.
_x
=
margin ;
container.
_y
=
margin ;
setVisible
(
!
hide
) ;
}
J'ai défini une propriété name qui permet de définir un nom à l'image à afficher, une propriété url de type String qui permet de définir l'url de l'image externe et un paramètre booléen hide qui permet de définir si le clip contenant l'image est visible par défaut lorsque l'on instancie pour la première fois cette classe.
Je profite du constructeur de la classe Picture pour créer dynamiquement dans le movieClip passé en paramètre un clip vide « container » qui servira de conteneur principal pour charger l'image par la suite.
Les 2 seules méthodes importantes de cette classe sont isVisible():Boolean et setVisible(b:Boolean).
Si vous prenez le temps de lire le contenu de cette classe à mon avis vous n’aurez aucun problème pour comprendre son utilisation. Il ne faut pas oublier l'existance de la méthode accept() définie dans la classe abstraite AbstractVisitable (c'est tout de même cette méthode qui compte le plus finalement pour utiliser le DP Visitor :D).
Pour tester cette classe, il vous suffit dans le fla qui contient votre clip qui servira de base pour charger une image de taper le code suivant :
import
test.
visitor.
diaporama.
Picture ;
var
url
:
String
=
"library/picture1.jpg"
;
var
picture:
Picture =
new
Picture
(
"picture1"
,
picture_mc,
url
) ;
Pour le moment il ne se passe pas grand-chose, c'est normal nous n'avons pas encore mis en place les IVisitor de cette classe qui vont lui permettre d'étendre ses fonctionnalités.
2 - Mise en place des IVisitor de la classe Picture. Les IVisitor sont des extensions de la classe qui pour chaque classe implémente une fonctionnalité précise liée à la classe principale.
La classe 'ClearVisitor' : permet de vider simplement le contenu d'une instance de la classe Picture.
import
test.
visitor.
diaporama.
Picture;
import
vegas.
core.
CoreObject;
import
vegas.
errors.
IllegalArgumentError;
import
vegas.
util.
visitor.
IVisitable;
import
vegas.
util.
visitor.
IVisitor;
/**
* This class clear the view of a Picture instance.
* @author eKameleon
*/
class
test.
visitor.
diaporama.
visitors.
ClearVisitor extends
CoreObject implements
IVisitor
{
/**
* Creates a new ClearVisitor instance.
*/
public
function
ClearVisitor
(
)
{
super
(
);
}
/**
* Clear a Picture object. Visit the IVisitable object.
*/
public
function
visit
(
o:
IVisitable ):
Void
{
if
(
o instanceof
Picture)
{
trace
(
"> "
+
this
+
" visit "
+
o) ;
var
view =
Picture
(
o).
view ;
if
(
view.
container.
picture !=
null
)
{
view.
container.
picture.
removeMovieClip
(
) ;
}
}
else
{
throw
new
IllegalArgumentError
(
this
+
" 'visit' method failed, the argument of this method must be a Picture instance."
) ;
}
}
}
Il est donc important de noter dans cette classe l'implémentation de l'interface « IVisitor » et la mise en place de la méthode visit() qui prend en paramètre un object « IVisitable ». La méthode visit() teste si l'instance passée en paramètre est bien un objet de type Picture et si c'est le cas il vide son contenu. À noter que j'utilise dans cette méthode les erreurs définies dans VEGAS dans le package vegas.errors.*. Je reviendrais dans un autre tutoriel sur la gestion des erreurs.
Toutes mes classes de base héritent de la classe vegas.core.CoreObject qui implémente quelques fonctionnalités de base importantes liées à VEGAS.
La classe 'HideVisitor' : permet simplement de lancer la fonction setVisible(b:Boolean) d'une instance de la classe Picture avec une valeur false (cacher l'image)
import
test.
visitor.
diaporama.
Picture;
import
vegas.
core.
CoreObject;
import
vegas.
errors.
IllegalArgumentError;
import
vegas.
util.
visitor.
IVisitable;
import
vegas.
util.
visitor.
IVisitor;
/**
* This class hide the view of a Picture instance.
* @author eKameleon
*/
class
test.
visitor.
diaporama.
visitors.
HideVisitor extends
CoreObject 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
{
if
(
o instanceof
Picture)
{
trace
(
"> "
+
this
+
" visit "
+
o) ;
Picture
(
o).
setVisible
(
false
) ;
}
else
{
throw
new
IllegalArgumentError
(
this
+
" 'visit' method failed, the argument of this method must be a Picture instance."
) ;
}
}
}
La classe 'ShowVisitor' : permet simplement de lancer la fonction setVisible(b:Boolean) d'une instance de la classe Picture avec une valeur true (afficher l'image)
import
test.
visitor.
diaporama.
Picture;
import
vegas.
core.
CoreObject;
import
vegas.
errors.
IllegalArgumentError;
import
vegas.
util.
visitor.
IVisitable;
import
vegas.
util.
visitor.
IVisitor;
/**
* This class release the view and the properties of a Picture instance.
* @author eKameleon
*/
class
test.
visitor.
diaporama.
visitors.
ReleaseVisitor extends
CoreObject implements
IVisitor
{
/**
* Creates a new ReleaseVisitor instance.
*/
public
function
ReleaseVisitor
(
)
{
super
(
);
}
/**
* Release a Picture object. Visit the IVisitable object.
* Initialize the Picture and remove the target reference.
*/
public
function
visit
(
o:
IVisitable ):
Void
{
if
(
o instanceof
Picture)
{
trace
(
"> "
+
this
+
" visit "
+
o) ;
Picture
(
o).
view.
removeMovieClip
(
) ;
Picture
(
o).
url
=
null
;
Picture
(
o).
name
=
null
;
}
else
{
throw
new
IllegalArgumentError
(
this
+
" 'visit' method failed, the argument of this method must be a Picture instance."
) ;
}
}
}
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.
import
test.
visitor.
diaporama.
Picture;
import
test.
visitor.
diaporama.
visitors.
ClearVisitor;
import
test.
visitor.
diaporama.
visitors.
ShowVisitor;
import
vegas.
core.
CoreObject;
import
vegas.
errors.
IllegalArgumentError;
import
vegas.
errors.
Warning;
import
vegas.
events.
Delegate;
import
vegas.
util.
visitor.
IVisitable;
import
vegas.
util.
visitor.
IVisitor;
/**
* This class load an external picture in a Picture instance.
* @author eKameleon
*/
class
test.
visitor.
diaporama.
visitors.
LoaderVisitor extends
CoreObject implements
IVisitor
{
/**
* Creates a new LoaderVisitor instance.
*/
public
function
LoaderVisitor
(
bAutoShow:
Boolean
)
{
super
(
);
autoShow =
bAutoShow ;
}
/**
* Defined if the picture is show when is loading.
*/
public
var
autoShow:
Boolean
;
/**
* Load a Picture object with this current url. Visit the IVisitable object.
*/
public
function
visit
(
o:
IVisitable ):
Void
{
if
(
o instanceof
Picture)
{
trace
(
"> "
+
this
+
" visit "
+
o) ;
var
view =
Picture
(
o).
view ;
var
url
:
String
=
Picture
(
o).
url
;
// clear the picture.
Picture
(
o).
accept
(
new
ClearVisitor
(
) ) ;
if
(
url
.
length
>
0
)
{
var
listener:
Object
=
{}
;
listener.
onLoadInit
=
Delegate.
create
(
this
,
onLoadInit
,
Picture
(
o) ) ;
var
target
:
MovieClip
=
view.
container.
createEmptyMovieClip
(
"picture"
,
1
) ;
var
loader:
MovieClipLoader
=
new
MovieClipLoader
(
) ;
loader.
addListener
(
listener) ;
loader.
loadClip
(
url
,
target
) ;
}
else
{
throw
new
Warning
(
this
+
" the picture '"
+
o +
"' can't be loading with an empty url property."
) ;
}
}
else
{
throw
new
IllegalArgumentError
(
this
+
" 'visit' method failed, the argument of this method must be a Picture instance."
) ;
}
}
/**
* Invoqued when the loading of a Picture is finished and initialized.
*/
public
function
onLoadInit
(
target
:
MovieClip
,
picture:
Picture ):
Void
{
trace
(
"> "
+
this
+
" picture "
+
picture +
" is loading and init."
) ;
if
(
autoShow ==
true
)
{
picture.
accept
(
new
ShowVisitor
(
) ) ;
}
}
}
J'utilise dans l'exemple la classe MovieClipLoader pour charger l'image définie par la propriété url définie dans l'instance de type Picture. Il est intéressant de voir qu'il est possible de définir des paramètres dans la fonction constructeur d'un objet qui implémenter l'interface IVisitor. Vous pouvez pour fini regarder le contenu de la méthode visit() de cette classe, j'utilise un appel direct à la fonction HideVisitor dans cette méthode pour cacher l'image au lancement du chargement. Dans la méthode onLoadInit la classe ShowVisitor est utilisée pour afficher à nouveau l'image une fois chargée (si autoShow == true bien entendu).
Comme vous pouvez le voir, l'intérêt de ces classes est d'isoler toutes les fonctionnalités de la classe Picture sans surcharger celle-ci avec des méthodes inutiles. Je considère que le pattern Visitor est un bon compromis pour créer des plugins sur mes classes. À moi ensuite d'installer ou non les plugins selon mes besoins dans les différentes applications que je mettrais en place par la suite.
3 - Pour finir, voyons le code principal de l'application qui se trouve dans le Picture.fla :
/**
* Design Pattern Visitor with VEGAS.
* eKameleon - 2006
*/
import
test.
visitor.
diaporama.
visitors.*
;
import
test.
visitor.
diaporama.
Picture ;
var
url
:
String
=
"library/picture1.jpg"
;
var
picture:
Picture =
new
Picture
(
"picture1"
,
picture_mc,
url
) ;
// Hide the picture
picture.
accept
(
new
HideVisitor
(
)) ;
// load the picture by default and auto show when is loading.
picture.
accept
(
new
LoaderVisitor
(
true
)) ;
this
.
onKeyDown
=
function
(
)
{
var
code:
Number
=
Key
.
getCode
(
) ;
switch
(
code)
{
case
Key
.
UP
:
{
picture.
accept
(
new
HideVisitor
(
)) ;
break
;
}
case
Key
.
DOWN
:
{
picture.
accept
(
new
ShowVisitor
(
)) ;
break
;
}
case
Key
.
LEFT
:
{
picture.
accept
(
new
ClearVisitor
(
)) ;
break
;
}
case
Key
.
RIGHT
:
{
picture.
url
=
"library/picture2.jpg"
;
picture.
accept
(
new
LoaderVisitor
(
)) ;
break
;
}
}
}
Key
.
addListener
(
this
) ;
trace
(
"> Press Key.UP to hide the picture."
) ;
trace
(
"> Press Key.DOWN to show the picture."
) ;
trace
(
"> Press Key.LEFT to clear the picture."
) ;
trace
(
"> Press Key.RIGHT to load the picture."
) ;
Vous voyez qu'il devient très simple à tout moment d'exécuter une commande externe de type IVisitor sur l'instance de la classe Picture qui est IVisitable.
IV. Conclusion▲
Reste maintenant à créer un petit diaporama pour afficher dans notre « vue » plusieurs images. Je vais attendre les 2 prochains tutoriels pour vous montrer comment il est possible d'utiliser le Design Pattern Observer et ensuite le modèle événementiel de VEGAS (encore mieux) pour gérer ce type d'application via un MVC propre et efficace.
Je vais pour le moment vous laisser le temps de lire correctement ce tutoriel et d'essayer à votre tour d'implémenter ce pattern avant de passer à la suite.
Pour ceux qui découvrent ce tutoriel et VEGAS du même coup, pour installer mon framework vous pouvez lire mon article à ce sujet : Installation des sources via Subversion.