P
'
t
i
t
e
C
h
a
t
t
e
 
spacer~ THERE'S NO PLACE LIKE 127.0.0.1 Articles | Connexion
 
~Le motif Etat

 Présentation

La qualité d'un système orienté objet peut être mesurée par rapport à la collaboration entre ses objets. En développant soigneusement ces mécanismes, à l'aide des Design Patterns, le programmeur peut aboutir à une architecture bien plus intelligible que s'il les ignorait.
 Sommaire


 introduction

Le motif de conception Etat (ou "State" en anglais) appartient à la catégorie comportementale. Celui-ci permet à un objet de modifier son comportement lorsque son état interne change. Ce changement apparaît comme un changement de classe.

Une fois de plus, les éditeurs de dessin constituent un parfait exemple pour illustrer nos propos. Dans ce type de logiciel, l'utilisateur peut sélectionner un outil particulier dans une trousse. Ainsi, il pourra choisir l'outil de sélection ou celui de construction de rectangles. Quel que soit celui pour lequel l'utilisateur opte, sa manipulation sera la même : dessiner un rectangle à l'aide de la souris en réalisant un cliquer/glisser. Nous sommes ici en présence d'une situation qui mérite l'implémentation du motif Etat. Selon que l'état de l'outil soit sélection ou rectangle, le comportement sera différent avec une même interface.

L'idée de base de ce modèle est d'introduire une classe abstraite appelée ToolState qui représente les états de l'outil. Celle-ci déclare l'interface commune à toutes les classes représentant les différents outils proposés par l'application. Ainsi, les sous-classes de ToolState implémentent les comportements relatifs aux états Selection et Rectangle.

Un tel modèle doit, pour fonctionner, s'inscrire dans un contexte. Il s'agit d'une classe, dans notre exemple nous aurions le contexte GraphicEditor, encapsulant un objet état et qui lui délègue toutes les requêtes relatives à ce dernier. De la sorte, le développeur d'un éditeur graphique redirigera tous les événements de la souris vers son objet ToolState.


 Convertisseur numérique

L'image d'une application de dessin montre de manière évidente les notions de contexte et d'état. Néanmoins, notre Design Pattern peut trouver d'autres applications, parfois moins évidentes. Le code source présent sur le CD-Rom correspond à une application de conversion numérique. Ce logiciel peut convertir un nombre d'une base décimale, octale ou hexadécimale vers l'un des ces trois bases. Notre programme encapsule deux objets de conversion, une source et une destination. Il est évident que le comportement de nos convertisseurs dépend de leur état, soit de la base numérique sélectionnée. Le principe du programme se veut relativement simple : un objet de conversion peut convertir un nombre décimal dans sa base associée. L'objet source donne alors sous forme décimale le nombre entré par l'utilisateur à l'objet destination, qui effectue la transformation finale. Le schéma 1 récapitule ceci en proposant un exemple de conversion d'un nombre de base octale vers une base hexadécimale.



Schéma 1. Processus de conversion numérique.

La première étape de l'implémentation du modèle consiste à définir la classe Etat, ici intitulée Converter. Le listing 1 présente la classe employée dans le programme. Celle-ci propose trois méthodes. La première nommée isValid() vérifie la validité de la valeur entrée au clavier par l'utilisateur. Dans les faits, nous ferons appel à une expression régulière. Ainsi, le contexte n'exécutera jamais une conversion si la valeur ne répond pas aux exigences de isValid(). La deuxième méthode correspond au processus de conversion proprement dit. Cette dernière reçoit en paramètre le convertisseur source et la valeur à traiter. L'objet va alors demander à la source de convertir cette valeur en décimal pour ensuite la traiter et retourner le résultat sous forme de chaîne de caractères. Cette conversion intermédiaire se trouve implémentée par la troisième et dernière méthode, toDecimal().

#Listing 1
class Converter:
  def isValid(self, value):
    return None

  def convertFrom(self, fromConverter, value):
    return None

  def toDecimal(self, value):
    return None
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
A chaque fois, un nombre se voit passé en paramètre par le contexte. Nous aurions également pu créer un constructeur semblable à celui-ci :

def __init__(self, context):
  self.context = context
       
      
JextCopier dans Jext
Cela permettrait de bénéficier de l'interface du contexte pour récupérer les valeurs. Dans l'application présentée, le contexte désigne la fenêtre principale, instance de la classe mainFrame. Au sein de celle-ci se trouvent deux champs texte et deux groupes de trois boutons radio. Ces boutons autorisent le changement d'état des objets de conversion initialisés de la sorte :

self.converters = [DecimalConverter(), \
  HexadecimalConverter(), \
  OctalConverter()]
self.fromConverter = self.converters[0]
self.toConverter = self.converters[1]
       
      
JextCopier dans Jext
La source évoquée plus haut s'avère être l'objet self.fromConverter tandis que la destination n'est autre que self.toConverter. LAPI PyQT nous autorise d'associer à chaque bouton radio un identificateur. Nous avons opté pour un indice correspondant à un des états de la liste self.converters. Par exemple, les boutons radio "Octal" possèdent l'identificateur 2, car l'instance du convertisseur octal se trouve à l'indice 2 de notre liste. Lors de la sélection d'un des boutons radio, les méthodes suivantes se voient invoquées :

def selectTo(self, id):
  self.toConverter = self.converters[id]

def selectFrom(self, id):
  self.fromConverter = self.converters[id]
       
      
JextCopier dans Jext
Vous pouvez constater combien le changement d'état se révèle simple. Les objets de la liste self.converters sont des instances d'états concrets, donc de classes héritant de la classe Converter décrite précédemment. Le listing 2 propose l'exemple du convertisseur hexadécimal. Enfin, la méthode convert() du contexte, exécutée après un clic sur le bouton de conversion, fait appel aux deux premières méthodes de notre interface :

def convert(self):
  if self.fromConverter.isValid(self.txtFrom.text()):
    self.txtTo.setText(self.toConverter.convertFrom( \
  self.fromConverter, self.txtFrom.text()))
       
      
JextCopier dans Jext
#Listing 2
class HexadecimalConverter(Converter):
  def isValid(self, value):
    return re.match("(0x)?[0-9A-Fa-f]+$", str(value))

  def convertFrom(self, fromConverter, value):
    return hex(fromConverter.toDecimal(value))

  def toDecimal(self, value):
    return int(str(value), 16)
       
      
JextCopier dans Jext

 Conséquences

L'architecture que nous avons pu mettre en place à l'aide du modèle de conception Etat nous a évité la rédaction de longues structures conditionnelles. Ceci s'explique par le fait que nous avons isolé les comportements spécifiques d'état. L'ajout, la modification ou la suppression de code spécifique à un état n'a donc aucune incidence sur les autres.



Schéma 2. Structure du motif de conception Etat.

L'utilisation du motif Etat rend en outre les transitions d'état plus explicites. Contrairement à une architecture reposant sur des valeurs de variables d'état et de longues déclarations conditionnelles, la transition d'un état vers un autre se caractérise par la modification d'une seul variable, la variable état du contexte. De plus cette dernière correspond à une instance dont la classe diffère suivant l'état. Pour finir, les objets états peuvent aisément se voir partagés par plusieurs contextes s'ils ne possèdent pas de variables d'instance et donc que l'état qu'ils représentent est intégralement exprimé dans leur type. C'est le cas des objets Converter que nous venons d'étudier.


 Téléchargements

Vous pouvez télécharger l'exemple complet.



par Romain Guy
romain.guy@jext.org
http://www.jext.org
Dernière mise à jour : 14/10/2006



 
#ProgX©2005 Mathieu GINOD - Romain GUY - Erik LOUISE