Le motif MVC
Définition
Le motif Modèle-Vue-Controleur est un motif composé. C’est l’utilisation conjointe des motifs Composite, Strategie et Observateur pour permettre une bonne séparation entre l’interface utilisateur et les règles de gestion.
Principe
Le controleur est basé sur le motif Strategie. C’est lui qui fait la jonction entre les deux autres. C’est lui qui sait qui appeler, que faire quand il reçoit un certain signal. La vue elle est un motif Composite, chaque element graphique est considéré comme un composant. Le controleur ne s’adresse qu’au compsé principal pour qu’il répercute les modifications dans tout ces composants. Le modèl implémente le motif Observateur. En fait il vas être observé. Effectivement, la vue à besoin pour certain de ces composants d’être tenue au courant de l’état du modèle.
L’exemple par le code
Cet exemple est un peu long, mais le motif est complexe (ou plutôt compsé ;-) ). Nous allons réaliser un outil de conversion. C’est un peu obsélète, mais ça serviras d’exemple simple.
Commençons par le Controleur, c’est lui le centre de notre application. Les deux require sont notre vue et notre modèle que nous verrons plus bas.
require 'convert-model'
require 'money-convert-tool'
class ConvertControler
def initialize
@montant = Montant.new
@view = MoneyConvertToolGlade.new(self)
@montant.add_observer(@view)
end
def close
Gtk.main_quit
end
def change_amount(from=0.0, devise="")
@montant.montant = (Taux::convert_to_euro(from,devise))
end
end
Gtk.init
controler = ConvertControler.new
Gtk.main
On est obligé de lancé GTK à partir d’ici. C’est genant pour l’abstraction, mais c’est limité au maximum de ce que nous permet la librairie Ruby-Gnome
Le controleur initialise tout les éléments à prendre en compte:
- La vue (attribut @view)
- le model (attribut @montant)
On ajoute la vue comme un observeur du montant. Ensuite on défini la strategie pour chaque actions. Ici simplement close et change_amount. close vas juste fermer la fenetre, change_amount prend deux paramètres: le montant à convertir, et la devise dans lequel il faut le convertir. On appel l’objet Taux pour effectuer le travail. On met à jour avec le resultat l’objet Montant pour repercuter le resultat.
Voici maintenant la vue, attention c’est un gros morceau.
require 'libglade2'
class MoneyConvertToolGlade
include GetText
def initialize(controler)
@controler = controler
window = Gtk::Window.new
window.set_title('Convertisseur')
window.signal_connect('destroy') { self.on_close_clicked }
vertical_box_main = Gtk::VBox.new(true, 6)
horizontal_box_line2 = Gtk::HBox.new(true, 6)
horizontal_box_line2.pack_start(Gtk::Label.new('Montant en Euro'), false, true, 6)
@montant_a_convertir = Gtk::Entry.new
@montant_a_convertir.signal_connect('changed') {self.on_insert_amount }
horizontal_box_line2.pack_start(@montant_a_convertir)
vertical_box_main.pack_start(horizontal_box_line2)
horizontal_box_line3 = Gtk::HBox.new(true, 6)
horizontal_box_line3.pack_start(Gtk::Label.new('Montant converti'), false, true, 6)
@montant_converti = Gtk::Entry.new
horizontal_box_line3.pack_start(@montant_converti)
@combox_from_devise = Gtk::ComboBox.new(true)
@combox_from_devise.signal_connect('changed') { self.on_from_devise_changed }
horizontal_box_line3.pack_start(@combox_from_devise)
vertical_box_main.pack_start(horizontal_box_line3)
horizontal_box_line4 = Gtk::HBox.new(true, 6)
button_close = Gtk::Button.new("Fermer")
button_close.signal_connect('clicked') { self.on_close_clicked }
horizontal_box_line4.pack_start(button_close)
vertical_box_main.pack_start(horizontal_box_line4)
window.add(vertical_box_main)
window.show_all
init_taux_liste(Taux::get_labels)
end
def update(new_montant)
@montant_converti.text = new_montant.to_s
end
def init_taux_liste(taux_list)
taux_list.each do |taux|
@combox_from_devise.append_text(taux)
end
@combox_from_devise.active=0
end
def on_close_clicked
@controler.close
end
def on_insert_amount
@controler.change_amount(@montant_a_convertir.text.to_f, @combox_from_devise.active_text)
end
def on_from_devise_changed
@controler.change_amount(@montant_a_convertir.text.to_f, @combox_from_devise.active_text)
end
end
L’initialize contient la création complète de l’interface, l’utilisation de Glade (http://glade.gnome.org/: , http://ruby-gnome2.sourceforge.jp/hiki.cgi ) nous gêne par rapport à la gestion des signaux, des appels à cet objet. Le reste des methodes sont là pour les interactions avec l’interface graphique.
Et pour finir, le modèle. Pour plus de facilité, deux objets sont regroupé ici. Ce n’est pas une obligation ;-)
require 'observer'
module Taux
def self.convert_to_euro(montant=0, from_devise=nil)
return montant * Taux_list[from_devise]
end
Taux_list = {}
Taux_list.store("Francs",6.55957)
Taux_list.store("Livres",0.67)
Taux_list.store("Dollars",1.20)
Taux_list.store("Yen",143.35)
Taux_list.store("Roubles",143.35)
Taux_list.store("Dirhams",4.44)
def self.get_labels
return Taux_list.keys.sort
end
end
class Montant
include Observable
attr_accessor :montant
def initialize
@montant = 0
end
def montant=(value = 0)
changed
@montant = value
notify_observers(@montant)
end
end
Le module Taux ne sert que pour effectuer des calcul de conversion Euro -> Devise. On pourrais l’étoffer pour pouvoir faire des conversions dans plusieurs sens… mais ce n’est pas le sujet. On lui à ajouter une method pour l’obtention de la liste des libellés de devise prise en compte pour pouvoir construire la liste des propositions. Encore une fois on pourrais pousser l’exemple et utiliser le motif Iterator, mais ce n’est pas l’objet de cet article. Cependant il faudrais le faire pour être plus propre.
L’objet montant peut paraitre ridicule. Effectivement il ne contient qu’un chiffre. Mais il est important de l’isoler pour permettre l’utilisation du motif observateur. L’interface graphique vas s’abonné à ce dernier pour pouvoir être au courant dès qu’il y a du changement. Dans le cas d’objet plus nombreux et plus complexe que dans notre exemple ça devient très interessant.
Voici un tarball contenant tout les fichiers rubyfr.org_motif_mvc.tar.gz


