Description
Le design pattern command permet la transformation d'une action à exécuter en un objet autonome contenant tous les détails de l'action. Par cette transformation, nous pouvons :
- Paramétrer des méthodes avec différentes actions.
- Planifier l'exécution des méthodes.
- Mettre les actions en file d'attente
- Annuler des opérations effectuées.
Mise en situation
🎮 Nous allons développer le fonctionnement d'une console :
🕹️ Nous allons avons une manette qui définit les touches de base. 👾 Nous allons avoir un jeu qui définit les mouvements qui lui sont appropriés.
Dans ce cas, nous avons deux acteurs qui vont interagir. L'on pourrait simplement penser à créer un objet bouton et lui lier simplement des actions, au moyen d'énormément de sous-classes. Plus vous allez avancer, plus vous découvrez énormément de bugs, car le composant de votre bouton (touche) garde en mémoire la classe précédente, vous avez des sous-classes qui ne fonctionnent plus etc.
C'est là que le pattern decorator vient à la rescousse :
- Implémentation d'une séparation des préoccupations.
- Vous avez une interface command à implémenter pour chaque nouvelle. commandes
- Chaque jeu va avoir ses commandes qui seront exécutées par la manette. Vu que toute commande implémente la commande, on aura juste à exécuter la méthode du jeu pour savoir l'action à réaliser.
Avantages et inconvénients
Avantages | Inconvénients |
---|---|
✔️Responsabilité unique: découplage des classes de traitement et des classes d'exécution. Cela permet de différencier les commandes | ❌Complexité par l'ajout de nombreuses couches entre le receveur et l'émetteur de commande. |
✔️Flexibilité: l'ajout d'une nouvelle commande n'a aucun impact sur le reste du code. | |
✔️Commande composite: permet de réunir plusieurs comment, en une commande plus complexe. |
Quand l'utiliser ?
💡 Il est souvent utilisé du côté du GUI. 💡 Vous pouvez l'utiliser dans le cas d'implémentation d'actions qui ont besoin d'être découplées.
Diagramme UML
Dans le cas de cet exemple, nous développons une simulation d'une manette en ligne de console qui prendra en compte les mouvements d'un jeu. Dans notre cas nous prendrons Zelda afin de donner un exemple.
Implémentation
Commençons par créer la base de notre design pattern : l'interface Command.
Command
package com.design.command.command;
public interface Command {
void execute();
}
Notre interface de base n'est pas très compliquée pour l'instant, elle demande l'implémentation d'une fonction execute
qui permettra de définir l'action de notre manette. Maintenant que nous avons terminé notre interface, ajoutons notre receiver qui sera le jeu qui donnera les commandes en fonction de l'action.
ZeldaCharacterReceiver
package com.design.command.character;
public class ZeldaCharacterReceiver {
private String name;
public void moveUp() {
System.out.println(getName() + " Jumping up");
}
public void moveDown() {
System.out.println(getName() + " Jumping down");
}
public void moveLeft() {
System.out.println(getName() + " Moving left");
}
public void moveRight() {
System.out.println(getName() + " Moving right");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Notre jeu est créé, il définit les actions suivantes :
moveUp
: qui affichera un message d'un mouvement vers le haut.moveDown
: qui affichera un message d'un mouvement vers la droite.moveLeft
: qui affichera un message d'un mouvement vers la gauche.moveRight
: qui affichera un message d'un mouvement vers le bas.
Mais aussi une propriété qui définira le nom du jeu, histoire de vous permettre d'ajouter plusieurs jeux de la même structure à l'avenir.
Maintenant, définissons la manette de notre jeu, le controller
. Ce controller permettra facilement de définir plusieurs controllers à l'avenir :
Controller
package com.design.command.model;
import com.design.command.command.Command;
public class Controller {
private Command upCommand, downCommand, leftCommand, rightCommand;
public Controller(Command upCommand, Command downCommand, Command leftCommand, Command rightCommand) {
//concrete Command
upCommand = upCommand;
downCommand = downCommand;
leftCommand = leftCommand;
rightCommand = rightCommand;
}
public void arrowUp() {
upCommand.execute();
}
public void arrowDown() {
downCommand.execute();
}
public void arrowLeft() {
leftCommand.execute();
}
public void arrowRight() {
rightCommand.execute();
}
}
Notre manette est définie ! Maintenant, comme vous pouvez le voir, nous avons besoin de 4 commandes pour définir les actions de notre manette. Ces actions auront pour but de réagir en fonction du receiver (jeu) qu'elles utilisent :
ZeldaUpCommand
package com.design.command.command.zelda;
import com.design.command.character.ZeldaCharacterReceiver;
import com.design.command.command.Command;
public class ZeldaUpCommand implements Command {
private ZeldaCharacterReceiver zeldaCharacterReceiver;
public ZeldaUpCommand(ZeldaCharacterReceiver zeldaCharacterReceiver) {
this.zeldaCharacterReceiver = zeldaCharacterReceiver;
}
@Override
public void execute() {
zeldaCharacterReceiver.moveUp();
}
}
ZeldaDownCommand
package com.design.command.command.zelda;
import com.design.command.character.ZeldaCharacterReceiver;
import com.design.command.command.Command;
public class ZeldaDownCommand implements Command {
private ZeldaCharacterReceiver zeldaCharacterReceiver;
public ZeldaDownCommand(ZeldaCharacterReceiver zeldaCharacterReceiver) {
this.zeldaCharacterReceiver = zeldaCharacterReceiver;
}
@Override
public void execute() {
zeldaCharacterReceiver.moveDown();
}
}
ZeldaLeftCommand
package com.design.command.command.zelda;
import com.design.command.character.ZeldaCharacterReceiver;
import com.design.command.command.Command;
public class ZeldaLeftCommand implements Command {
private ZeldaCharacterReceiver zeldaCharacterReceiver;
public ZeldaLeftCommand(ZeldaCharacterReceiver zeldaCharacterReceiver) {
this.zeldaCharacterReceiver = zeldaCharacterReceiver;
}
@Override
public void execute() {
zeldaCharacterReceiver.moveLeft();
}
}
ZeldaRightCommand
package com.design.command.command.zelda;
import com.design.command.character.ZeldaCharacterReceiver;
import com.design.command.command.Command;
public classZeldaRightCommand implements Command {
private ZeldaCharacterReceiver zeldaCharacterReceiver;
public ZeldaRightCommand(ZeldaCharacterReceiver zeldaCharacterReceiver) {
this.zeldaCharacterReceiver = zeldaCharacterReceiver;
}
@Override
public void execute() {
zeldaCharacterReceiver.moveRight();
}
}
Parfait ! Nos 4 commandes sont implémentées et chacune d'elles utilise l'objet receiver qui lui est assigné. Ainsi, il utilisera la commande définie plus haut. Testons notre code dans la classe Main
de notre application :
Main
import com.design.command.command.zelda.ZeldaUpCommand;
import com.design.command.model.Controller;
public class Main {
public static void main(String[] args) {
//Create receivers
ZeldaCharacterReceiver zeldaCharacterReceiver = new ZeldaCharacterReceiver();
zeldaCharacterReceiver.setName("Zelda");
//Commands
ZeldaUpCommand zeldaUpCommand = new ZeldaUpCommand(zeldaCharacterReceiver);
ZeldaDownCommand zeldaDownCommand = new ZeldaDownCommand(zeldaCharacterReceiver);
ZeldaLeftCommand zeldaLeftCommand = new ZeldaLeftCommand(zeldaCharacterReceiver);
ZeldaRightCommand zeldaRightCommand = new ZeldaRightCommand(zeldaCharacterReceiver);
//Invoker
Controller nintendo = new Controller(zeldaUpCommand, zeldaDownCommand, zeldaLeftCommand, zeldaRightCommand);
nintendo.arrowDown();
nintendo.arrowLeft();
nintendo.arrowRight();
nintendo.arrowUp();
}
}
Resultat :
Zelda Jumping down
Zelda Moving left
Zelda Moving right
Zelda Jumping up
Ici, nous avons donc fini notre implémentation. Ce que nous pouvons remarquer :
- Nous avons des commandes différentes pour notre jeu, ce qui permettra d'ajouter d'autres commandes si nous voulons ajouter un jeu.
- Notre controller n'utilise que des objets de type commande, ce qui permettra d'augmenter la flexibilité en prenant des commandes implémentant l'interface.
- Ajouter un jeu se fera de la même façon que l'implémentation de Zelda.
- Nous avons gagné en flexibilité où chaque action est un objet
Command
.