~ / M2PGI / IHM / tps / doc /

HOWTO PAC

© 2007—2008 - Renaud Blanch

Ce document propose une implémentation du modèle d'architecture d'application interactive PAC avec le langage Tcl.

Implémentation de PAC en Tcl/Tk

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/.

Control

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]
}

Abstraction et Presentation

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
}

Exemple

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.

Strucure de l'agent 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
}

Modification de la valeur de l'abstraction

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
}

Édition au travers de la présentation

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
}

Le patron observer en Tcl/Tk

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

Introspact

L'outil introspact pourra vous être utile lors de la mise au point de vos programmes.

mise à jour : 18 octobre 2007