© 2007—2008 - Renaud Blanch
Ce document propose une implémentation du modèle d'architecture d'application interactive PAC avec le langage Tcl.
Tcl/Tk étant un langage très souple, nous allons utiliser une réalisation minimale de PAC.
En particulier, comme le modèle objet gmlObject n'offre pas de restriction de visibilité sur les attributs d'une instance, nous n'utiliserons pas d'accésseurs comme c'est courant en Java.
Les classes de base proposées sont au nombre de 3, une pour chaque facette du modèle PAC : l'abstraction, la présentation et enfin le contrôleur qui maintient le lien entre les deux autres facettes.
Elles sont regroupées dans le fichier PAC.tcl
que vous pouvez placer dans votre répertoire IHM/
.
Chacune de ces classes ne fait rien d'autre que maintenir la structure à l'exécution des agents, c'est-à-dire pour chaque contrôleur, un lien vers son abstraction et sa présentation, ainsi qu'une liste de ses fils et un lien vers son père.
# Control -- # # control facet for PAC architecture # method Control constructor {{parent ""} {abstraction ""} {presentation ""} {children {}}} { set this(parent) $parent set this(abstraction) $abstraction set this(presentation) $presentation set this(children) $children if {$this(parent) != ""} { $this(parent) append $objName } } method Control destructor {} { if {$this(parent) != ""} { $this(parent) remove $objName } foreach child $this(children) { $child dispose } foreach facet {presentation abstraction} { if {$this($facet) != ""} { $this($facet) dispose set this($facet) "" } } } method Control append {child} { lappend this(children) $child } method Control remove {child} { set child_index [lsearch $this(children) $child] set this(children) [lreplace $this(children) $child_index $child_index] }
Les facettes abstraction et présentation sont encore plus simples puisqu'elles ne font que maintenir un lien vers leur contrôleur respectif.
# Abstraction -- # # abstraction facet for PAC architecture # method Abstraction constructor {control} { set this(control) $control } # Presentation -- # # presentation facet for PAC architecture # method Presentation constructor {control} { set this(control) $control }
Dans sa forme la plus simple, un agent PAC peut stocker une valeur et l'afficher. Nous allons créer un tel agent que nous allons appeler Value.
On peut alors commencer l'abstraction en ajoutant simplement un attribut à l'abstraction élémentaire :
source [file join [file dirname [info script]] .. PAC.tcl] # ValueA -- # # value abstraction inherit ValueA Abstraction method ValueA constructor {control value} { this inherited $control set this(value) $value }
La présentation peut être constituée d'une simple fenêtre :
# ValueP -- # # a value presentation inherit ValueP Presentation method ValueP constructor {control} { this inherited $control set this(window) [toplevel .${objName}] wm protocol $this(window) WM_DELETE_WINDOW "$this(control) dispose" set this(label) [label $this(window).label -justify right] pack $this(label) -expand 1 -fill both } method ValueP destructor {} { destroy $this(window) }
Le contrôleur agrège les deux facettes précédentes :
# Value -- # # a value controler inherit Value Control method Value constructor {parent value} { ValueA ${objName}_abst $objName $value ValueP ${objName}_pres $objName this inherited $parent ${objName}_abst ${objName}_pres } method Value destructor {} { this inherited }
Lorsqu'on édite la valeur stockée par l'abstraction, il faut refléter ses changements sur la présentation.
Nous emploierons donc la terminologie edit
pour les modifications concernant l'abstraction et change
pour ceux concernant la présentation.
Pour pouvoir modifier la valeur stockée dans l'abstraction et propager ce changement via le contrôleur à la présentation, il faut alors ajouter les méthodes suivantes :
method ValueA edit {value} { set this(value) $value $this(control) change } method ValueP change {value} { $this(label) configure -text $value } method Value change {} { $this(presentation) change [$this(abstraction) attribute value] }
Appeler edit
sur l'abstraction appelle alors change
sur le contrôleur, qui appelle alors à son tour change
sur la présentation.
% Value value "" 10 % $value(abstraction) attribute value 10 % $value(abstraction) edit 20 % $value(abstraction) attribute value 20 % [$value(presentation) attribute label] cget -text 20 % value dispose
Pour assurer que la présentation soit conforme à l'abstraction dès le début, il convient d'ajouter une ligne au constructeur de l'abstraction pour forcer la mise à jour sitôt l'instance construite :
method Value constructor {parent value} { ValueA ${objName}_abst $objName $value ValueP ${objName}_pres $objName this inherited $parent ${objName}_abst ${objName}_pres this change }
Si l'on veut pouvoir éditer la valeur au travers de sa présentation, il faut ajouter le mécanisme inverse à celui qui vient d'être mis en place, i.e. pouvoir modifier le texte affiché par la présentation et que cette modification aille éditer l'abstraction via le contrôleur.
Il faut modifier la présentation pour utiliser un interacteur qui soit éditable, et lui ajouter une nouvelle méthode edit
qui sera déclenchée par l'appui sur <Return>
:
method ValueP constructor {control} { this inherited $control set this(window) [toplevel .${objName}] wm protocol $this(window) WM_DELETE_WINDOW "$this(control) dispose" set this(entry) [entry $this(window).entry -justify right] pack $this(entry) -expand 1 -fill both bind $this(entry) <Return> "$objName edit" } method ValueP change {value} { $this(entry) delete 0 end $this(entry) insert 0 $value } method ValueP edit {} { set newvalue [$this(entry) get] $this(control) edit $newvalue }
Enfin, il faut que le contrôleur transmette le message à l'abstraction grâce à sa méthode edit
:
method Value edit {newvalue} { $this(abstraction) edit $newvalue }
Dans le modèle PAC, l'abstraction est chargée de notifier de ses modifications pour que la présentation soit mise à jour en conséquence et reflète à tout moment l'état courant du noyau fonctionnel.
Ici, on a cette garantie si la valeur stockée dans l'abstraction est modifiée uniquement avec la méthode edit
de la classe ValueA
qui assure l'appel à change
dans le contrôleur.
Une manière plus sûre de garantir cette cohérence est de surveiller la variable et de répercuter tout changement automatiquement.
Un tel mécanisme est souvent utilisé, c'est un patron (pattern) qui s'appelle observer.
En Tcl la commande trace
permet de définir une commande qui sera appelée dès qu'une variable change de valeur, ce qui permet de réaliser très simplement ce patron :
method ValueA constructor {control value} { this inherited $control set this(value) $value trace add variable this(value) write "$objName change" } method ValueA change {args} { $this(control) change } method ValueA edit {value} { set this(value) $value }
À présent, toute modification de la valeur stockée dans l'abstraction, même si elle ne se fait pas par le biais de la méthode edit
, sera répercutée sur la présentation.
% Value value "" 10 value % $value(abstraction) setAttribute value 12 12 % [$value(presentation) attribute entry] get 12
L'outil introspact pourra vous être utile lors de la mise au point de vos programmes.
mise à jour : 18 octobre 2007