Implémentation d'un pattern MVC par signaux : class Signal
| Date : | 17.01.2006 |
| Auteur : | Frédéric Logier |
Attention
Ce code est encore un brouillon, il pourra être amené à évoluer.
De quoi s’agit-il ?
L’objectif est de proposer une classe de communication permettant d’implémenter le motif MVC à destination de tout type de logiciel, sauf web RoR étant là pour ça. J’ai fais le choix d’utiliser une communication par signaux entre les modules, par simplicité. Ce n’est en aucun cas une implémentation rigoureuse du motif MVC, l’objectif étant de se rapprocher de l’esprit. Les personnes intéressées par un MVC conventionnel peuvent étudier Le motif observer.
Comment cela fonctionne ?
Dans un motif MVC, le Modèle et la Vue sont censés communiquer via le Controleur. L’idée est d’utiliser des signaux pour cette communication. Lorsque l’on développe une interface en RG2 on utilise de fait les signaux. En effet lorsque l’on créé un bouton avec Glade on sélectionne un signal parmis la liste des signaux gérés par le widget GtkButton ou un de ses parents. A ce signal on associe une fonction de traitement (callback) qu’il suffira de créer dans la classe appelant le projet Glade. Il est intéressant de savoir que ces signaux ne sont pas spécifiques à l’interface graphique.
La GLib
En effet les signaux sont proposés par la GLib, une bibliothèque de bas niveau utilisée par GTK+. La GLib implémente les GObject qui sont les objets de GTK+ permettant la programmation object en C. La libglade utilise la GLib pour la gestion des signaux avec l’IHM. Il est donc tout a fait possible d’utiliser directement la GLib pour gérer nos propres signaux. Pour cela, cette classe hérite de la GLib::Object et son instance sera un GObject.
La classe Signal
Cette classe a pour objectif de faciliter la gestion de nos signaux. Elle utilise la passerelle GLib de RG2 vers les signaux. Elle permet :
- la création
- l’émission
- la connexion
- le blocage et le déblocage
- ...
Cette classe est un singleton ce qui nous garantie de n’avoir qu’une seule instance. Elle pourra être instancié au tout début de notre programme à l’intérieur d’une constante par exemple. Ainsi en passant par notre constante on pourra accéder à notre object signal depuis n’importe où. Exemple :
SIGNAL = Signal.instance
Utilisation
Dans la classe plus bas 2 signaux sont créés, CrefreshNB et VrefreshYP. Chaque signal que l’on voudra créer devra être défini dans la classe Signal. Pour chaque signal une méthode signal_do_nomdusignal devra être créé même si elle n’est pas utilisée. Une normalisation des noms des signaux permet rapidement de connaitre leur destination, leur action et sur quel objet. Le C majuscule signifie Controleur et le V Vue. refresh est l’action du signal enfin les 2 dernières lettres précisent l’object interne à l’application. Le premier signal est donc à destination du Controleur et a pour but de rafraichir un Notebook. Le deuxième à destination de la vue rafraichit un treeview nommé YP. A vous de créer votre norme de nommage si celle-ci ne vous convient pas.
CrefreshNB
Prenons l’exemple de ce signal. On devine que le Modèle va émettre ce signal pour le Controlleur qui émettra à son tour un signal pour la Vue. Le Controleur devra s’y connecter de manière à l’intercepter. En effet un signal n’est pas émit à destination de quelque chose ! Il est tout simplement émit. A charge à l’objet souhaitant le gérer de s’y connecter.
Il paraît évident, mais autant le préciser, qu’il ne faut pas se connecter au moment de l’émission. En effet le Controleur se connecte au signal au moment de son initialisation, de cette manière :
SIGNAL.connect("CrefreshNB") {
puts "CrefreshNB"
}
Oui, on fourni à la méthode connect une proc. Cette proc est le code que le Controleur veut exécuter. Ainsi le code est bien défini côté Controleur, même s’il est exécuté par l’object Signal via le yield. Le motif MVC est bien respecté, dans l’esprit tout du moins :).
Le Modèle émet le signal comme ceci :
SIGNAL.emit("CrefreshNB", 1, "toto")
On remarquera que lors de l’emission on peut transmettre un nombre de paramètres illimités. Le type de paramètre est précisé dans le signal_new. Ici ce sont 1 Integer et 1 String mais cela pourrait être un Array comme pour le signal VrefreshYP.
Maintenant on souhaite bien évidement utiliser ce paramètre dans le code du Controleur. C’est ici que la magie de Ruby via le yield intervient :
SIGNAL.connect("CrefreshNB") {|data1,data2|
puts data1.to_s + " " + data2
}
La proc affiche : “1 toto” !
Voici la classe et la fin de cette présentation. En attendant un prochain tutoriel présentant une application RG2 utilisant cette classe.
# signal.rb -
# Copyright (C) 2006 Frédéric Logier
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
require 'glib2'
require 'singleton'
class Signal < GLib::Object
include Singleton
type_register
signal_new("CrefreshNB", # name
GLib::Signal::RUN_FIRST, # flags
nil, # accumulator (XXX: not supported yet)
nil, # return type (void == nil)
Integer, String # parameter types
)
signal_new("VrefreshYP", # name
GLib::Signal::RUN_FIRST, # flags
nil, # accumulator (XXX: not supported yet)
nil, # return type (void == nil)
Array # parameter types
)
def initialize
super(nil)
end
def emit(signal_name, *data)
begin
signal_emit(signal_name, *data)
rescue => e
puts "Alert : signal_emit exception on the signal named " + signal_name + " !"
puts "Exception : " + e.to_str
puts "Backtrace :"
puts e.backtrace
end
end
def connect(signal_name)
begin
signal_connect(signal_name){|obj, *signal_param|
yield *signal_param
}
rescue => e
puts "Alert : signal_connect exception on the signal named " + signal_name + " !"
puts "Exception : " + e.to_str
puts "Backtrace :"
puts e.backtrace
end
end
def handler_block(handler_id)
signal_handler_block(handler_id)
end
def handler_unblock(handler_id)
signal_handler_unblock(handler_id)
end
#######
private
#######
def signal_do_CrefreshNB(int,string)
end
def signal_do_VrefreshYP(array)
end
end


