La délégation : à quoi ça sert

Un cas très courant nécessitant la délégation se présente dans l'exemple suivant : un objet graphique (héritant de MovieClip) possède un bouton qui exécute une fonction de l'objet lorsque on lui clique dessus :

Class MonObjet extends MovieClip{

	private var monBouton:MovieClip;

	public function MonObjet(){	
		monBouton.onRelease = this.faitUnTruc;
	}
	
	public function faitUnTruc(){
		trace(this);
	}

}

Explication : je passe la fonction faitUnTruc en référence à monBouton.onRelease. Ainsi, lorsque je vais cliquer sur monBouton, la fonction faitUnTruc sera exécutée. Enfin dans la fonction faitUnTruc, je fais un trace de l'objet courant (this).

Et bien dans cette situation, this fera référence non pas à l'occurrence de MonObjet, mais à celle du bouton ! La faute au passage par référence de la fonction faitUnTruc à monBouton.onRelease : la fonction faitUnTruc s'exécute bien, mais dans le "scope" du bouton, c'est à dire comme une fonction du bouton, et non de l'objet.

Mise en place de la délégation avec la classe Delegate

C'est à ce moment précis qu'arrive la classe Delegate pour contourner ce problème de manière simple et élégante. Cette classe, écrite par Mike Chambers, est disponible depuis Flash MX 2004. Voici la modification à apporter à notre exemple de départ :

import mx.utils.Delegate;

Class MonObjet extends MovieClip{

	private var monBouton:MovieClip;

	public function MonObjet(){	
		monBouton.onRelease = Delegate.create(this, faitUnTruc);
	}
	
	public function faitUnTruc(){
		trace(this);
	}

}

On commence par importer la classe qui se trouve dans le package mx.utils. Ensuite, au lieu de faire référence directement à la fonction faitUnTruc, on fait référence à la méthode statique Delegate.create avec deux arguments : le premier est le scope, c'est à dire l'objet quoi doit garder la référence, et le deuxième est la fonction à exécuter. Cette fois bingo, this dans la fonction faitUnTruc désigne bien l'objet.

Pour résumer, lorsque j'écris :

Delegate.create(UnObjet, faitUnTruc);

Cela peut se traduire par :

"Exécute la fonction faitUnTruc comme étant une fonction de l'objet UnObjet"

Passer des arguments

Passée l'euphorie (si si...), on tombe assez vite sur le prochain problème (ah flûte...) : Delegate ne permet pas de passer de paramètres à la fonction appelée. Imaginons que la méthode faitUnTruc doit pouvoir recevoir un ou plusieurs paramètres pour s'exécuter correctement, on ne peut pas les spécifier au moment de la délégation.

Il existe heureusement plusieurs techniques pour pouvoir le faire quand même :

Rajouter des propriétés à la méthode appelante

Ne jamais oublier LE truc chouette en Actionscript : (presque) tout est objet, donc une fonction est un objet. Et comme n'importe quel objet je peux donc lui rajouter des propriétés... c'est le principe de cette première solution :

import mx.utils.Delegate;

Class MonObjet extends MovieClip{

	private var monBouton:MovieClip;

	public function MonObjet(){
		var d = Delegate.create(this, faitUnTruc);
		d.p1 = "toto";
		d.p2 = 42;
		monBouton.onRelease = d;
	}
	
	public function faitUnTruc(){
		trace(arguments.caller.p1+","+arguments.caller.p2);
	}

}

Je passe en référence à monBouton.onRelease une fonction qui est elle-même une référence à Delegate.create(this, faitUnTruc), à laquelle j'ai rajouté au préalable deux propriétés p1 et p2. Celle-ci est ensuite disponible dans la fonction faitUnTruc grâce à arguments.caller. C'est une solution simple et rapide, mais pas très propre car elle nécessite de connaître le nom des propriétés dans la fonction appelée.

Relegate

Tekool nous propose sur son site une méthode plus élégante et moins verbeuse sous la forme d'une fonction Relegate qui ressemble en tous points à Delegate, excepté qu'elle accepte des paramètres supplémentaires qui seront transmis à la fonction appelée. Voici comment l'utiliser dans notre exemple :

import Relegate;

Class MonObjet extends MovieClip{

	private var monBouton:MovieClip;

	public function MonObjet(){
		monBouton.onRelease = Relegate.create(this, faitUnTruc, "toto", 42);
	}
	
	public function faitUnTruc(p1,p2){
		trace(p1+","+p2);
	}

}

La délégation et ActionScript 3

Attention car avec ActionScript 3 tout ceci va changer ! Oui en gros tout ce que j'ai écrit juste au dessus sera bientôt obsolète (et l'est déjà pour les gens qui développent sous Flex 2) !

Le tout nouveau modèle événementiel, basé sur celui du DOM Level 3, permet donc que le scope soit conservé lors du passe d'une fonction par référence ! Ainsi en reprenant l'exemple inital en AS3 :

package{

	Class MonObjet extends MovieClip{

		private var monBouton:MovieClip;

		public function MonObjet(){
			monBouton.onRelease = this.faitUnTruc;
		}
	
		public function faitUnTruc(){
			trace(this);
		}

	}

}

le this à l'intérieur de la fonction faitUnTruc désignera bien l'occurrence de MonObjet, et non plus le bouton, ce qui est quand même plus simple ;)