Commande (patron de conception)
En génie logiciel, commande est un patron de conception (design pattern) de type comportemental qui encapsule la notion d'invocation. Il permet de séparer complètement le code initiateur de l'action, du code de l'action elle-même[1]. Ce patron de conception est souvent utilisé dans les interfaces graphiques où, par exemple, un élément de menu peut être connecté à différentes Commandes de façon que l'objet d'élément de menu n'ait pas besoin de connaître les détails de l'action effectuée par la commande[2].
À utiliser lorsqu'il y a prolifération de méthodes similaires, et que le code de l'interface devient difficile à maintenir.
Symptômes :
- Les objets possèdent trop de méthodes publiques à l'usage d'autres objets.
- L'interface est inexploitable et on la modifie tout le temps.
- Les noms des méthodes deviennent de longues périphrases.
Un objet commande sert à communiquer une action à effectuer, ainsi que les arguments requis. L'objet est envoyé à une seule méthode dans une classe, qui traite les commandes du type requis. L'objet est libre d'implémenter le traitement de la commande par un switch, ou un appel à d'autres méthodes (notamment des méthodes surchargées dans les sous-classes)[3]. Cela permet d'apporter des modifications aux commandes définies simplement dans la définition de la commande, et non dans chaque classe qui utilise la commande.
Diagramme de classes
Le patron de conception commande peut être représenté par le diagramme de classes UML suivant :
Exemple
C#
using System;
using System.Collections.Generic;
namespace CommandPattern
{
public interface ICommand
{
void Execute();
}
/* The Invoker class */
public class Switch
{
private List<ICommand> _commands = new List<ICommand>();
public void StoreAndExecute(ICommand command)
{
_commands.Add(command);
command.Execute();
}
}
/* The Receiver class */
public class Light
{
public void TurnOn()
{
Console.WriteLine("The light is on");
}
public void TurnOff()
{
Console.WriteLine("The light is off");
}
}
/* The Command for turning on the light - ConcreteCommand #1 */
public class FlipUpCommand : ICommand
{
private Light _light;
public FlipUpCommand(Light light)
{
_light = light;
}
public void Execute()
{
_light.TurnOn();
}
}
/* The Command for turning off the light - ConcreteCommand #2 */
public class FlipDownCommand : ICommand
{
private Light _light;
public FlipDownCommand(Light light)
{
_light = light;
}
public void Execute()
{
_light.TurnOff();
}
}
/* The test class or client */
internal class Program
{
public static void Main(string[] args)
{
Light lamp = new Light();
ICommand switchUp = new FlipUpCommand(lamp);
ICommand switchDown = new FlipDownCommand(lamp);
Switch s = new Switch();
string arg = args.Length > 0 ? args[0].ToUpper() : null;
switch(arg)
{
case "ON":
s.StoreAndExecute(switchUp);
break;
case "OFF":
s.StoreAndExecute(switchDown);
break;
default:
Console.WriteLine("Argument \"ON\" or \"OFF\" is required.");
break;
}
}
}
}
Java
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
/*the Command interface*/
public interface Command {
void execute();
}
/*the Invoker class*/
public class SwitchManager {
private final HashMap<String, Command> commandMap = new HashMap<>();
private List<Command> history = new ArrayList<Command>();
public void register(String commandName, Command command) {
commandMap.put(commandName, command);
}
public void execute(String cmdName) {
Command command = commandMap.get(cmdName);
if (command == null) {
throw new IllegalStateException("no command registered for " + cmdName);
}
this.history.add(command); // optional
command.execute();
}
}
/*the Receiver class*/
public class Light {
public void turnOn() {
System.out.println("The light is on");
}
public void turnOff() {
System.out.println("The light is off");
}
}
/*the Command for turning on the light - ConcreteCommand #1*/
public class FlipUpCommand implements Command {
private Light light;
public FlipUpCommand(Light light) {
this.light = light;
}
@Override // Command
public void execute(){
this.light.turnOn();
}
}
/*the Command for turning off the light - ConcreteCommand #2*/
public class FlipDownCommand implements Command {
private Light light;
public FlipDownCommand(Light light) {
this.light = light;
}
@Override // Command
public void execute() {
this.light.turnOff();
}
}
/*The test class or client*/
public class PressSwitchDemo {
public static void main(String[] args){
Light lamp = new Light();
Command switchOn = new FlipUpCommand(lamp);
Command switchOff = new FlipDownCommand(lamp);
SwitchManager mySwitchManager = new SwitchManager();
mySwitchManager.register("on", switchOn);
mySwitchManager.register("off", switchOff);
mySwitchManager.execute("on");
mySwitchManager.execute("off");
}
}
Java 8
Utilisant les lambda expression et une interface fonctionnelle.
import java.util.HashMap;
/**
* The Command functional interface.<br/>
*/
@FunctionalInterface
public interface Command {
public void apply();
}
/**
* The CommandFactory class.<br/>
*/
public class CommandFactory {
private static CommandFactory instance; //Singleton
private final HashMap<String, Command> commands;
private CommandFactory() {
this.commands = new HashMap<>();
}
public void addCommand(String name, Command command) {
this.commands.put(name, command);
}
public void executeCommand(String name) {
if ( this.commands.containsKey(name) ) {
this.commands.get(name).apply();
}
}
public void listCommands() {
// using stream (Java 8)
System.out.println("Commands enabled :");
this.commands.keySet().stream().forEach(System.out::println);
}
/* Singleton pattern */
public static CommandFactory getInstance() {
if (instance == null) {
instance = new CommandFactory();
}
return instance;
}
/* Factory pattern */
public void create() {
// commands are added here using lambda. It also possible to dynamically add commands without editing code.
addCommand("Light on", () -> System.out.println("Light turned on"));
addCommand("Light off", () -> System.out.println("Light turned off"));
}
}
/**
* Main/test/client class.
*/
public class Main {
public static void main(String[] args) {
CommandFactory cf = CommandFactory.getInstance();
cf.create();
cf.executeCommand("Light on");
cf.listCommands();
}
}
Perl
# exemple de style "switch" :
sub doCommand {
my $me = shift;
my $cmd = shift; $cmd->isa('BleahCommand') or die;
my $instr = $cmd->getInstructionCode();
if($instr eq 'PUT') {
# PUT logic here
} elsif($instr eq 'GET') {
# GET logic here
}
# etc
}
# exemple de style "appel de méthode" :
sub doCommand {
my $me = shift;
my $cmd = shift; $cmd->isa('BleahCommand') or die;
my $instr = $cmd->getInstructionCode();
my $func = "process_" . $instr;
return undef unless defined &$func;
return $func->($cmd, @_);
}
# exemple de style "sous-classe".
# on suppose que %commandHandlers contient une liste de pointeurs d'objets.
sub doCommand {
my $me = shift;
my $cmd = shift; $cmd->isa('BleahCommand') or die;
my $insr = $cmd->getInstructionCode();
my $objectRef = $commandHandlers{$instr};
return $objectRef ? $objectRef->handleCommand($cmd, @_) : undef;
}
Comme Perl dispose d'un AUTOLOAD, le principe pourrait être émulé. Si un package voulait effectuer un ensemble de commandes arbitrairement grand, il pourrait recenser toutes les méthodes undefined grâce à AUTOLOAD, puis tenter de les répartir (ce qui suppose que %commandHandlers contient une table de référence, dont les clés sont les noms des méthodes) :
sub AUTOLOAD {
my $me = shift;
(my $methodName) = $AUTOLOAD m/.*::(\w+)$/;
return if $methodName eq 'DESTROY';
my $objectRef = $commandHandlers{$methodName};
return $objectRef ? $objectRef->handleCommand($methodName, @_) : undef;
}
Cela convertit les appels aux différentes méthodes dans l'objet courant, en appels à une méthode handleCommand dans différents objets. Cet exemple utilise Perl pour adapter un patron de conception à base d'objets Commandes, dans une interface qui en est dépourvue.
PHP
abstract class Command
{
abstract function execute();
}
class Executor
{
private array $history = [];
public function saveAndExecute(Command $cmd)
{
$this->history[] = $cmd; // optional
$cmd->execute();
}
}
class Light
{
public function turnOn()
{
writeLine('Light is on.');
}
public function turnOff()
{
writeLine('Light is off.');
}
}
class CommandOn extends Command
{
public function __construct(private readonly Light $light)
{
}
public function execute()
{
$this->light->turnOn();
}
}
class CommandOff extends Command
{
public function __construct(private readonly Light $light)
{
}
public function execute()
{
$this->light->turnOff();
}
}
function Client($commandString)
{
$lamp = new Light();
$cmdOn = new CommandOn($lamp);
$cmdOff = new CommandOff($lamp);
$executor = new Executor();
switch ($commandString) {
case 'ON':
$executor->saveAndExecute($cmdOn);
break;
case 'OFF':
$executor->saveAndExecute($cmdOff);
break;
default:
writeLine('Please ask for "ON" or "OFF" only.');
}
}
function writeLine($text) {
print $text.'<br/>';
}
Client('ON');
Client('OFF');
Cela affiche :
Light is on. Light is off.
Notes et références
- « Command · Design Patterns Revisited · Game Programming Patterns », sur gameprogrammingpatterns.com (consulté le )
- « Commande »
- (en) « Service Subscribers & Locators »