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