Le motif command
Définition
Le motif commande est le principe d’encapsuler des requêtes (au sens large, pas forcement SQL). Cela permet au client de construire ça demande.
Principe
Un objet sert de télécommande universel, on lui donneras les diverse commande dont on a besoin. Il s’occuperas de gérer la pile d’execution, sont historique, et l’execution à proprement parlé.
L’exemple par le code
Nous allons poser les bases d’un pseudo logiciel de dessin.
Voyons l’objet principal qui regrouperas les commandes.
class Drawer
attr :command_list
def initialize
@command_list = {}
@last_command = []
end
def do(command)
if @command_list[command] != nil then
@last_command.push(@command_list[command])
@command_list[command].do.call
end
else
#~ raise Exception.new("la commande '#{command}' n'est pas enregistré")
puts "la commande '#{command}' n'est pas enregistré"
end
end
def undo
if !@last_command.empty?
@last_command.pop.undo.call
else
#~ raise Exception.new("plus de command à annuler")
puts "plus de command à annuler"
end
end
end
Bien. Dans l’initialisation, on prépare deux listes. L’une nous servira à stocké l’historique des commandes, l’autre contriendra les commandes enregistrées. Noté les deux lignes en commentaire. Selon votre besoin, vore envie, plutôt que de renvoyer un message comme nous le faisons là, vous pourriez lever une exception.
Voyons maintenant deux commandes qui nous servirons de base.
class Trace_line
def Trace_line.do
return proc {puts "je trace une ligne"}
end
def Trace_line.undo
return proc {puts "j'efface la dernière ligne"}
end
end
class Trace_round
attr :radius
def initialize(radius)
@radius = radius
end
def do
return proc {puts "je trace un un rond de rayon: #{@radius}"}
end
def undo
return proc {puts "j'efface le dernier rond de rayon : #{@radius}"}
end
end
Ces deux objets ne sont pas forcement très logique, mais disons que ça nous permet de voir un cas de figure interessant. Pour la ligne, je n’ai pas mis d’attribut. Du coup pas besoin de faire une instance, d’ou l’utilisation de methode dites de classe.
Par contre, et ça permet de voir qu’on peut définir ce que l’on souhaite comme commande, l’objet Trace_round à lui un attribut: son rayon. Nous allons maintenant voir comment cela se passe en testant tout ça.
draw = Drawer.new
draw.command_list["line"] = Trace_line
draw.command_list["round"] = Trace_round.new(12)
draw.do("line")
draw.do("round")
draw.do("toto")
draw.undo
draw.undo
draw.undo
Bien, nous avons créé un objet Drawer, et nous lui glissons nos deux commandes. Trace_line est passer sous forme de classe alors que Trace_round lui est passer sous forme d’objet. C’est l’utilisation de l’objet ruby Proc qui permet cette souplesse, merci Ruby !
Ensuite nous demandons à notre objet d’executer les commandes, dont une qui n’a pas été enregistrée, et de défaire 3 commandes alors que nous n’en avons lancé que 2 valide…
Voilà le résultat:
je trace une ligne je trace un un rond de rayon: 12 la commande 'toto' n'est pas enregistré j'efface le dernier rond de rayon : 12 j'efface la dernière ligne plus de command à annuler
Et voilà, les débuts du remplaçant de the Gimp ;-)
Attention: les librairies de type Marshall pour la sérialisation ne peuvent sérialiser les objets Proc. Ceci dit nous pourrions les remplacer par de vrai objets…


