Command

La programmation avec design patterns

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com
L'encapsulation d'une requête dans l'objet peut être nécessaire dans certaines situations. L'exemple bien connu sont des interfaces graphiques où une action peut être déclenchée soit grâce à un click du souris, soit à cause d'une touche sur le clavier. Ces actions sont possibles, entre autres, grâce au design pattern commande.

Ce dessign pattern est donc un modèle comportemental dans lequel la commande à exécuter est encapsulée dans un objet. L'exécution se fait depuis un émetteur vers le récepteur. Les deux acteurs ne se connaissent pas. Ils sont totalement découpés l'un de l'autre.

Les acteurs suivants participent dans ce pattern :
- l'invocateur : son rôle est d'exécuter les commandes.
- le récepteur : il est le destinataire des la requête.
- le client : la classe qui fait le lien entre l'invocateur et le récepteur.
- la commande : c'est l'interface qui définit la méthode execute(), dont le rôle consiste à notifier le récepteur d'effectuer une opération.

Regardons cela sur un exemple concret. Tout d'abord, le client et la commande :

// command
interface IncrementatorInterface{
public void execute();
}

// clients
class SimpleIncrementator implements IncrementatorInterface{
private final IncrementationReceiver receiver;

public SimpleIncrementator(final IncrementationReceiver receiver){
this.receiver=receiver;
}

@Override
public void execute(){
this.receiver.incrementI();
}
}

class QuadripleIncrementator implements IncrementatorInterface{
private final IncrementationReceiver receiver;

public QuadripleIncrementator(final IncrementationReceiver receiver){
this.receiver=receiver;
}

@Override
public void execute(){
this.receiver.incrementQuadripleI();
}
}

On remarque la liaison forte entre les clients et le récepteur (ici en tant que l'instance IncrementationReceiver). La commande possède seulement une méthode, execute(), implémentée par chaque client. Regardons maintenant le récepteur :


// receiver
class IncrementationReceiver{
private int i=0;

public void incrementQuadripleI(){
this.i++;
this.i++;
this.i++;
this.i++;
}

public void incrementI(){
this.i++;
}

public int getI(){
return this.i;
}
}

C'est lui qui reçoit les requêtes. Dans notre exemple, une de ses méthodes d'incrémentation est appélée par le client : soit l'incrémentation quadriple, soit l'incrémentation simple. Cependant, ses invocations ne se font pas directement par le client. C'est l'invocateur qui s'occupe d'appeler le bon client et sa méthode. Le voici d'ailleurs :

// invoker
class IncrementationInvoker{
private IncrementatorInterface incrementator;

public void setIncrementator(final IncrementatorInterface incrementator){
this.incrementator=incrementator;
}

public void invoke(){
this.incrementator.execute();
}
}

Comme on a déjà mentionné, les seules choses qui l'intéressent sont le client (méthode setIncrementator()) et l'exécution de la commande (méthode invoke()). Et maintenant le code fonctionnel sous forme d'un test Junit :

public class IncrementatorTest{
@Test
public void test(){
final IncrementationInvoker incInvoker=new IncrementationInvoker();
final IncrementationReceiver incReceiver=new IncrementationReceiver();
final IncrementatorInterface quadriInc=new QuadripleIncrementator(incReceiver);
final IncrementatorInterface simpleInc=new SimpleIncrementator(incReceiver);
incInvoker.setIncrementator(simpleInc);
incInvoker.invoke();
assertEquals(1, incReceiver.getI());
incInvoker.setIncrementator(quadriInc);
incInvoker.invoke();
assertEquals(5, incReceiver.getI());
}
}

Le code dit tout pour lui. Dans un premier temps on initialise l'invocateur et le récepteur. Ensuite, on crée les instances de l'interface commande (IncrementatorInterface), auxquelles on passe le récepteur. Après c'est à l'invocateur de jouer. On voit qu'il prend le client et exécute la requête sur l'instance de l'IncrementationReceiver.

L'invocateur pourrait être encore plus utile si l'on voudrait stocker l'historique des incrémentations. La gestion de l'historique sera alors très simplifiée car elle se fera à travers un seul point d'entrée. On constate ceci sur notre code, suite à de petites modifications :

class IncrementationInvoker{
// ...
public void invoke(){
Archiver.archive("Incrementation made by : "+this.incrementator);
this.incrementator.execute();
}
}

class SimpleIncrementator implements IncrementatorInterface{
// ...
@Override
public String toString() {
return "SimpleIncrementator";
}
}

class QuadripleIncrementator implements IncrementatorInterface{
// ...
@Override
public String toString() {
return "QuadripleIncrementator";
}
}


Dans notre fichier d'archive on aurait alors deux entrées :

Incrementation made by : SimpleIncrementator
Incrementation made by : QuadripleIncrementator


Command est donc un design pattern adapté à des situations où un élément peut recevoir des événements de la part de plusieurs autres éléments.
Bartosz KONIECZNY 08-09-2013 17:36 design patterns
Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil Symfony2

Comment personnaliser la page 404 ?

Pour personnaliser la page 404 sous Symfony2 il faut surcharger le contrôleur par défaut. On peut l'achever en déterminant "exception_controller" dans le fichier de configuration. Supposons que nous utilisons le bundle ExceptionsErrorBundle pour gérer toute sorte des exceptions. Un des contrôleurs (NotFoundController) s'occupe de manipulations liées aux erreurs 404. Pour pouvoir utiliser ce contrôleur pour les erreurs type 404, il faut le déclarer dans le fichier de configuration (config.yml, config_dev.yml - en fonction de l'environnement) :

twig: 
    exception_controller: "ExceptionsErrorBundle :NotFound:handleException"