Commande
Intention
Commande est un patron de conception comportemental qui prend une action à effectuer et la transforme en un objet autonome qui contient tous les détails de cette action. Cette transformation permet de paramétrer des méthodes avec différentes actions, planifier leur exécution, les mettre dans une file d’attente ou d’annuler des opérations effectuées.
Problème
Imaginez-vous en train de développer un nouvel éditeur de texte. Vous travaillez actuellement sur la création d’une barre d’outils équipée d’un ensemble de boutons qui permettent d’effectuer diverses opérations dans l’éditeur. Vous avez créé une belle classe Bouton
qui peut être utilisée pour les boutons de la barre d’outils ou pour les boutons génériques des différentes boîtes de dialogue.
Ces boutons ont beau se ressembler, ils sont censés effectuer des actions différentes. Où va-t-on bien pouvoir mettre le code des actions que ces différents boutons déclenchent ? Le plus simple est de créer des tonnes de sous-classes où les boutons sont utilisés. Ces sous-classes vont contenir le code exécuté lors d’un clic sur un bouton.
Mais bientôt, vous allez remarquer que cette approche comporte de sérieux défauts. Le premier que nous allons aborder ici, est le fait que vous avez créé énormément de sous-classes. Lors de chaque modification de la classe Bouton
, vous risquez d’ajouter de nouveaux bugs dans le code. Pour faire simple, votre GUI (interface graphique utilisateur) est devenue un peu trop dépendante du code volatile de la logique métier.
Abordons à présent le défaut majeur. Certaines opérations, comme copier-coller du texte, doivent être appelées depuis différents endroits. Par exemple, un utilisateur peut cliquer sur un petit bouton « copier » de la barre d’outils, copier via le menu contextuel ou encore faire Ctrl+C
sur son clavier.
À l’époque où notre application n’avait qu’une barre d’outils, cela ne posait pas de problème de mettre l’implémentation des différentes actions dans la sous-classe du bouton. Vous pouviez écrire le code de la copie du texte dans la sous-classe BoutonCopier
sans problème. Mais lorsque vous implémentez des menus contextuels, des raccourcis ou autres fonctionnalités du même ordre, vous devez dupliquer le code de ces actions dans plusieurs classes ou rendre les menus dépendants des boutons, ce qui est encore pire.
Solution
Un logiciel bien conçu repose souvent sur le principe de séparation des préoccupations, qui est en général obtenu en décomposant l’application en plusieurs couches. Le cas le plus classique consiste à avoir une couche pour la GUI et une autre pour la logique métier. La couche GUI a la responsabilité d’afficher une belle image à l’écran, de détecter les actions de l’utilisateur et de l’application, et de les répercuter à l’écran. Mais en ce qui concerne l’exécution des traitements importants comme calculer la trajectoire vers la lune ou générer les rapports annuels, la couche GUI délègue le travail à la couche métier.
Dans le code, on se retrouve avec un objet GUI qui appelle une méthode de l’objet de la logique métier et lui passe des paramètres. Ce processus est souvent décrit comme un objet envoyant une demande à un autre.
Le patron de conception commande propose de ne pas envoyer ces demandes directement depuis les objets de la GUI. À la place, vous devez extraire le détail de ces demandes (un appel vers l’objet, le nom de la méthode et la liste des paramètres) dans une classe commande séparée avec une unique méthode qui déclenche cette demande.
Les objets commande servent de lien entre les diverses GUI et les objets métier. À partir de maintenant, l’objet GUI ne saura plus quel objet métier reçoit la demande et comment il la traite. L’objet GUI déclenche juste la commande, et cette dernière se charge de gérer les détails.
La prochaine étape consiste à implémenter la même interface pour toutes vos commandes. En général, elle ne possède qu’une seule méthode d’exécution et ne prend aucun paramètre. Cette interface vous permet d’utiliser diverses commandes avec le même demandeur (invoker), sans avoir à la coupler avec les classes concrètes des commandes. En bonus, vous pouvez dorénavant échanger les objets de la commande liés au demandeur, ce qui permet de modifier le comportement du demandeur lors de l’exécution.
Vous vous êtes peut-être rendu compte qu’il manquait une pièce du puzzle : les paramètres de la demande. Un objet GUI devrait les fournir à la couche métier. Mais comme la méthode d’exécution de la commande ne possède aucun paramètre, comment allons-nous nous en sortir ? En fait, la commande doit être pré configurée avec ces données ou être capable de les récupérer par elle-même.
Revenons à notre éditeur de texte. Après avoir mis en place le patron de conception commande, nous n’avons plus besoin de toutes ces sous-classes pour implémenter le comportement des différents clics. Vous pouvez vous contenter de mettre un seul attribut dans la classe de base Bouton
qui conserve une référence vers un objet commande et lui permet de lancer cette commande lors d’un clic.
Vous allez implémenter plusieurs classes commande pour chaque opération possible, et allez les relier avec les boutons en fonction du comportement attendu.
Vous pouvez implémenter tous les autres éléments de la GUI comme des menus, raccourcis ou des boîtes de dialogue complètes de la même manière. Ils seront reliés à une commande qui est exécutée lorsqu’un utilisateur interagit avec l’élément de la GUI. Vous l’avez probablement deviné, les éléments qui déclenchent les mêmes actions seront reliés aux mêmes commandes afin d’éviter la duplication de code.
Les commandes forment ainsi une couche intermédiaire très pratique et réduisent le couplage entre la GUI et les couches métier. Et nous n’avons abordé qu’une infime partie des bénéfices que le patron de conception commande peut apporter.
Analogie
Après une longue balade en ville, vous trouvez un restaurant sympa et vous vous asseyez à une table près de la fenêtre. Un serveur aimable se rapproche et prend votre commande en l’écrivant sur un bout de papier. Il se rend dans la cuisine et colle la commande sur le mur. Au bout d’un moment, la commande arrive au chef, qui la lit et prépare le plat. Le cuisinier place le repas sur un plateau avec la commande. Le serveur découvre le plateau, vérifie la commande pour s’assurer que tout est conforme, et l’apporte à votre table.
Le bout de papier joue le rôle de la commande. Il reste dans une file d’attente jusqu’à ce que le chef soit prêt à la servir. Il peut commencer à cuisiner directement, car la commande contient toutes les informations permettant au chef de préparer le plat. C’est mieux que de perdre du temps à venir vous demander les détails de la commande.
Structure
-
La classe Demandeur (invoker) a la responsabilité de l’envoi des demandes. Elle doit inclure un attribut qui stocke la référence à un objet commande. Le demandeur déclenche la commande plutôt que de l’envoyer directement au récepteur. Gardez bien en tête que le demandeur n’est pas responsable de la création de l’objet commande. En général, il s’agit d’une commande créée auparavant dans le constructeur du client.
-
L’interface Commande déclare habituellement une seule méthode pour exécuter la commande.
-
Les Commandes Concrètes implémentent plusieurs types de demandes. Une commande concrète n’est pas censée accomplir la tâche d’elle-même. Elle transmet l’appel aux objets de la logique métier. Mais pour simplifier le code, ces classes peuvent être fusionnées.
Les paramètres nécessaires à l’exécution d’une méthode sur un objet récepteur peuvent être déclarés comme des attributs de la commande concrète. Vous pouvez rendre les objets de la commande non modifiables en autorisant uniquement l’initialisation de ces attributs dans le constructeur.
-
La classe Récepteur porte la logique métier. À peu près n’importe quel objet peut prendre le rôle de récepteur. La majorité des commandes se contentent de passer la demande au récepteur et ce dernier se charge du traitement.
-
Le Client crée et configure les objets de la commande concrète. Il doit passer tous les paramètres de la demande (dont l’instance du récepteur) dans le constructeur de la commande. Ensuite, la commande qui en résulte peut être associée avec un ou plusieurs demandeurs.
Pseudo-code
Dans cet exemple, le patron de conception Commande aide à tracer l’historique des traitements et est en mesure d’annuler les modifications effectuées par un traitement.
Les commandes qui modifient l’état de l’éditeur (par exemple couper et coller) font une sauvegarde de son état avant de lancer le traitement associé à la commande. Une fois que la commande a été exécutée, elle est placée dans un historique de commandes (une pile d’objets commande) avec une sauvegarde de l’état actuel de l’éditeur. Si l’utilisateur a besoin d’annuler un traitement, l’application peut prendre la commande la plus récente de l’historique, lire la sauvegarde associée à l’état de l’éditeur et la restaurer.
Le code client (éléments de la GUI, historique des commandes, etc.) n’est pas couplé avec les classes de la commande concrète, car il interagit avec les commandes via l’interface commande. Cette approche vous permet d’ajouter de nouvelles commandes dans l’application sans endommager l’existant.
Possibilités d’application
Utilisez le patron de conception commande lorsque vous voulez paramétrer des objets avec des traitements.
Le patron de conception commande peut transformer l’appel d’une méthode en un objet autonome. Cette approche permet des utilisations très intéressantes : vous pouvez passer des commandes dans les paramètres d’une méthode, les stocker à l’intérieur d’objets, modifier les liens des commandes à l’exécution, etc.
Voici un exemple : vous développez un composant de GUI (un menu contextuel par exemple) et vous voulez que vos utilisateurs puissent configurer des éléments de menu qui déclenchent des traitements lorsqu’un utilisateur final clique dessus.
Utilisez ce patron si vous voulez mettre des traitements dans une file d’attente, programmer leur déclenchement, ou les exécuter à distance.
Comme pour tous les objets, une commande peut être sérialisée, ce qui veut dire qu’on la convertit en une chaîne de caractères qui peut facilement être écrite dans un fichier ou dans une base de données. La chaîne de caractère pourra ensuite être restaurée et utilisée par l’objet de la commande originale. Vous pouvez ainsi différer et planifier l’exécution de la commande. Mais ce n’est pas tout ! De la même manière, vous pouvez mettre une commande dans une file d’attente, l’historiser ou l’envoyer par le réseau.
Utilisez le patron de conception commande quand vous voulez implémenter des opérations réversibles.
Parmi les nombreuses techniques qui permettent d’implémenter la fonctionnalité annuler/rétablir, le patron de conception commande est probablement le plus populaire.
Pour pouvoir annuler des traitements, vous devez mettre en place un historique des actions effectuées. L’historique des commandes est une pile qui contient tous les objets des commandes exécutées avec l’état de l’application correspondant.
Cette méthode possède deux défauts. Premièrement, l’état d’une application n’est pas facile à sauvegarder, car une partie peut être privée. Ce problème peut être résolu grâce à l’intervention du patron de conception Mémento.
Deuxièmement, la sauvegarde de l’état risque de consommer beaucoup de RAM. Par conséquent, vous pouvez utiliser une autre alternative : plutôt que de rétablir l’état précédent, la commande effectue l’inverse de la modification, ce qui est malheureusement parfois impossible ou difficile à mettre en place.
Mise en œuvre
-
Déclarez l’interface commande avec une seule méthode d’exécution.
-
Commencez à récupérer les demandes et à les mettre dans les classes concrètes Commande qui implémentent l’interface commande. Chaque classe doit comporter un ensemble d’attributs qui permettent de stocker les paramètres des demandes, avec une référence à l’objet initial du récepteur. Ces valeurs doivent toutes être initialisées dans le constructeur de la commande.
-
Identifiez les classes qui vont jouer le rôle de Demandeurs. Créez les attributs de ces classes qui vont stocker les commandes. Les demandeurs doivent uniquement communiquer avec leurs commandes via l’interface commande. En général, ils ne créent pas les objets commande, c’est le code client qui s’en charge.
-
Plutôt que d’envoyer la demande directement au récepteur, modifiez les demandeurs afin qu’ils exécutent la commande.
-
Le client doit initialiser les objets dans l’ordre suivant :
- Créer les récepteurs.
- Créer les commandes et les associer avec les récepteurs si besoin.
- Créer les demandeurs et les associer avec les commandes.
Avantages et inconvénients
- Principe de responsabilité unique. Vous pouvez découpler les classes qui appellent des traitements, de celles qui les exécutent.
- Principe ouvert/fermé. Vous pouvez ajouter de nouvelles commandes dans le programme sans endommager le code client existant.
- Vous pouvez mettre en place une fonctionnalité annuler-rétablir.
- Vous pouvez différer l’exécution de vos traitements.
- Vous pouvez assembler plusieurs commandes simples en une seule plus complexe.
- Le code peut devenir plus compliqué, car vous créez une nouvelle couche entre les demandeurs et les récepteurs.
Liens avec les autres patrons
-
La Chaîne de responsabilité, la Commande, le Médiateur et l’Observateur proposent différentes solutions pour associer les demandeurs et les récepteurs.
- La chaîne de responsabilité envoie une demande ordonnée qui est passée tout au long d’une chaîne dynamique de récepteurs potentiels, jusqu’à ce que l’un d’entre eux décide de la traiter.
- La commande établit des connexions unidirectionnelles entre les demandeurs et les récepteurs.
- Le médiateur élimine les liens directs entre les demandeurs et les récepteurs, et les force à communiquer indirectement via un objet médiateur.
- L’observateur permet aux récepteurs de s’inscrire et de se désinscrire dynamiquement à la réception des demandes.
-
Les handlers dans la Chaîne de Responsabilité peuvent être implémentés comme des Commandes. Dans ce cas, vous pouvez lancer beaucoup de traitements différents sur le même objet contexte, représenté par une demande.
Vous pouvez cependant utiliser une autre approche dans laquelle la demande en elle-même est un objet commande. Vous pouvez ainsi exécuter le même traitement dans une série de différents contextes connectés par une chaîne.
-
Vous pouvez utiliser la Commande et le Mémento ensemble pour implémenter la fonctionnalité « annuler ». Dans ce cas, les commandes ont la responsabilité d’exécuter les divers traitements sur un objet cible. Les mémentos sauvegardent l’état de cet objet juste avant le lancement de la commande.
-
La Commande et la Stratégie peuvent se ressembler, car vous les utilisez toutes les deux pour paramétrer un objet avec une action. Cependant, ces patrons ont des intentions très différentes.
-
Vous pouvez utiliser la commande pour convertir un traitement en un objet. Les paramètres du traitement deviennent des attributs de cet objet. La conversion vous permet de différer le lancement du traitement, le mettre dans une file d’attente, stocker l’historique des commandes, envoyer les commandes à des services distants, etc.
-
La stratégie quant à elle, décrit généralement différentes manières de faire la même chose et vous laisse permuter entre ces algorithmes à l’intérieur d’une unique classe contexte.
-
-
Le Prototype se révèle utile lorsque vous voulez sauvegarder des copies de Commandes dans l’historique.
-
Vous pouvez traiter le Visiteur comme une version plus puissante du patron de conception Commande. Ses objets peuvent lancer des traitements sur divers objets dans différentes classes.