Utilisation de closures
Par Cédric Tabin le lundi 11.08.2008, 23:00 - ActionScript - Lien permanent
Comment définir le terme "closure" ? Globalement, c'est une fonction à l'intérieur d'une fonction. C'est quelque chose d'inexistant en Java pour l'instant (enfin presque, cf Scala) mais qui a toujours existé en ActionScript. On se souviendra notamment de la fameuse classe Delegate en AS2 qui utilisait cela ! Ce qui est intéressant, c'est le contexte d'exécution d'une closure. Petite démonstration en AS3...
Durant le développement de masapi, je m'en suis justement servi pour solutionner un problème très joliment. Pour commencer, ma classe se présente comme suit :
package { public class LoadableFileFactory { public function createLoaderFile(url:URLRequest):ILoadableFile { ... } public function createSoundFile(url:URLRequest):ILoadableFile { ... } public function createStreamFile(url:URLRequest):ILoadableFile { ... } public function createURLLoaderFile(url:URLRequest, dataFormat:String):ILoadableFile { ... } } }
J'en viens maintenant à ajouter une méthode qui en appellera dynamiquement une de ces quatre pour créer un objet ILoadableFile. Afin d'augmenter la modularité, je vais en crééer une autre qui va me renvoyer la fonction à appeler :
public function create(url:URLRequest, type:String):ILoadableFile { ... } public function getMethod(type:String):Function { ... }
Le type est une constante (issue de la classe LoadableFileType) qui comporte les valeurs suivantes : variables, text, binary, sound, swf, stream. La première implémentation qui vient à l'esprit est celle-ci :
public function create(url:URLRequest, type:String):ILoadableFile { var method:Function = getMethod(type); return method(url); } public function getMethod(type:String):Function { switch(type) { case "variables": case "text": case "binary": return createURLLoaderFile; case "sound": return createSoundFile; case "swf": return createLoaderFile; case "stream": return createStreamFile; } }
Le problème saute aux yeux : le deuxième paramètre de la méthode createURLLoaderFile ! Etant donné que dans la méthode create, l'appel est dynamique, il semble obligatoire de faire une implémentation du genre :
public function create(url:URLRequest, type:String):ILoadableFile { var method:Function = getMethod(type); if (method == createURLLoaderFile) return createURLLoaderFile(url, type); return method(url); }
Cette idée n'est pas bonne, tout simplement car si je veux utiliser ma méthode getMethod dans une autre classe, je vais devoir implémenter ce test à chaque fois. De plus il est très facile d'oublier de le faire... C'est là que la notion de closure intervient !
Au lieu de retourner une référence directe sur la méthode createURLLoaderFile, il suffit de créer une fonction qui passera le paramètre pour nous à celle-ci :
public function getMethod(type:String):Function { switch(type) { case "variables": case "text": case "binary": return function(url:URLRequest):ILoadableFile { return createURLLoaderFile(url, type); } //... } }
Et le tour est joué ! Lorsque je parle du contexte d'exécution, c'est le fait que malgré que la closure retournée par la méthode getMethod puisse être appelée bien plus tard (et dans une autre classe que LoadableFileFactory), elle appellera la méthode createURLLoader de la bonne instance avec les bons paramètres !
Il est également intéressant de noter que la référence du this a été correctement implémentée en AS3. En AS2, il aurait fallut préciser sur quelle instance l'appel createURLLoader est fait (la fameuse astuce du this) :
//code AS2 public function getMethod(type:String):Function { switch(type) { case "variables": case "text": case "binary": var factory:LoadableFileFactory = this; return function(url:URLRequest):ILoadableFile { return factory.createURLLoaderFile(url, type); } //... } }
Pour ceux qui aimeraient voir directement l'implémentation dans masapi, c'est par ici !
Commentaires
Coucou,
Je ne sais pas mettre le formatage code ici :p
Donc brute de pomme :
Pourquoi pas simplement
public function getILoadableFile(type:String):ILoadableFile
{
switch(type)
{
case "variables":
case "text":
case "binary":
return createURLLoaderFile(url, type);
//...
}
}
Ça évite une méthode (create n'existe plus..).
Bon, c'est sur, si c'était juste pour l'exemple :p
Salut,
Et ou prends-tu la variable 'url' ? L'idée de la fonction est justement de me dire quelle méthode appeler dynamiquement. Ceci dit, je pense qu'il serait mieux de la mettre en privée :P
@++
Pas mal du tout.
Je ne connaissais pas le terme "closure", meme si j'avais déjà essayé d'employer le système.
Ba...
Ou tu prends l'url..
Vraiment, je ne vois pas le probleme...
public function create(url:URLRequest, type:String):ILoadableFile
{
switch(type)
{
case "variables":
case "text":
case "binary":
return createURLLoaderFile (url, type);
case "sound":
return createSoundFile (url);
case "swf":
return createLoaderFile (url);
case "stream":
return createStreamFile (url);
}
}
Oui effectivement, c'est d'ailleurs comme ça que j'avais fait au début C'est juste une question de modularité très discutable ^^
@++
C'est tres futé comme technique et élégant
Au fait en Java la notion existe en partie, cela s'appelle les local classes.
Sun aurrait pu faire des Clotures ou delegates mais ca casserait completement le modele strictement objet de leur language.
Je suis pas un expert AS3, j'ai une question qui est plus générale mais qui concerne les clotures.
On est d'accord que quelque soit la signature de ta fonction interne sont type est "Function" qui est d'ailleur une classe.
La question est : Comme il n'y a pas de prototype pour les clotures, comment detecter se type de bug à la compilation (comme c#) ? Est-ce possible ou tout se fait au runtime ?
Salut,
Malheureusement tu ne peux pas faire ce type de check à la compilation... D'ailleurs lorsque tu mets des fonctions comme listeners d'un événements, si tu oublie le paramètre événementielle, tu n'auras aucun bug de compilation, juste un sévère crash à l'exécution. Pour cela, il faut généralement se méfier des closures et si possible passer par une autre solution, à moins de blinder le truc
@++