P
'
t
i
t
e
C
h
a
t
t
e
 
spacer~ I'M NOT AS THINK AS YOU DRUNK I AM !! Articles | Connexion
 
~Les motifs Adaptateur et Pont

 Présentation

Dans cette neuvième partie de notre périple consacré aux motifs de conception objet, nous allons étudier deux motifs structuraux. L'un des deux motifs se trouve particulièrement employé par les développeurs Java dans le cadre de la réalisation d'interfaces graphiques.
 Sommaire


 Introduction

Le premier motif de conception sur lequel nous allons nous pencher s'intitule Adaptateur (ou Adapter en anglais). Le rôle de ce motif consiste à convertir l'interface d'une classe en une autre répondant aux besoins spécifiés par le client. Ce motif garantit ainsi la collaboration entre des classes possédant des interfaces de programmation incompatibles.

Prenons l'exemple d'un programme quelconque écrit en Python. Imaginons que celui-ci offre une petite interface graphique relativement simple contenant deux listes de données et permettant le transfert des données d'une liste à l'autre. Parmi les différents toolkits accessibles en Python, nous connaissons aujourd'hui au moins wxPython et PyQT (sans oublier le traditionnel Tk). Chacun de ces toolkits propose pour sa classe de liste de données une interface différente. Ainsi, le widget wxPython wxListBox offre la possibilité d'ajouter des éléments par l'entremise de la méthode InsertItems(items, pos). PyQT par contre implémente la méthode insertStrList(list, pos = -1).

Puisque notre interface graphique se veut très simple, nous pouvons imaginer son écriture à l'aide de l'un des toolkits, puis plus tard, sa réécriture avec un second toolkit. Nous pourrions également décider de proposer son implémentation à l'aide des deux toolkits simultanément pour offrir plus de choix à l'utilisateur. Malheureusement, il peut s'avérer fastidieux de modifier tous les appels de méthodes relatifs à la manipulation de nos listes de données. Le motif Adaptateur apporte une solution élégante à ce problème.


 Adaptateur de classe et d'objet

Un motif adaptateur peut se voir implémenté sous deux formes particulières nommées adaptateur de classe et adaptateur d'objet. Quelle que soit la méthode retenue, le programmeur doit impérativement proposer une interface définissant les méthodes accessibles pour le client. Ainsi, pour notre programme Python nous proposerons la classe suivante :

class MultiList:
  def currentItem(self): pass
  def insertListOfStrings(self, list, pos = -1): pass
  def removeItemAt(self, pos): pass
  def getSelectedItems(self): return None
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Cette nouvelle interface doit être implémentée par l'adaptateur. Considérons l'approche de l'adaptateur d'objet. Le principe consiste à enfermer une instance de la classe à adapter au sein de l'adaptateur. Le listing numéro un propose un exemple plus parlant. Vous trouverez en fin d'article un exemple de programme simple utilisant l'adaptateur d'objet. Comme vous pouvez le constater toutes les méthodes héritées de notre interface d'adaptation invoquent des méthodes de l'objet self.list.

L'approche de l'adaptateur de classe consiste à hériter directement de l'objet à adapter. Dans le premier cas, nous avions hérité de QHBox (pour que l'adaptateur soit quand même un composant graphique) et de notre interface. L'adaptateur de classe hériterait quant à lui de MultiList et de QListBox. Les appels de méthodes sur l'objet self.list deviendraient alors des appels aux méthodes parentes. Chacun des approches possède son intérêt. Ainsi, si l'adaptateur de classe donne accès à toutes les autres méthodes parentes, l'adaptateur d'objet garantit l'accès aux seules méthodes compatibles avec l'interface de programmation du client, quel que soit le toolkit choisi. Notez également que l'adaptateur de classe ne peut fonctionner pour adapter une classe et toutes ses sous-classes.

Les adaptateurs se trouvent beaucoup employés par les programmeurs Java pour ne pas implémenter toutes les méthodes des interfaces d'écoute de AWT et Swing. Ainsi, plutôt que d'implémenter intégralement l'interface WindowListener, un programmeur Java créera généralement une classe dérivée de WindowAdapter qui propose une implémentation vide de toutes les méthodes de WindowListener.


 Le motif Pont

Le motif Pont, ou Bridge dans la langue du Gang Of Four, découple une abstraction de son implémentation. Ainsi, chacun des éléments pourra se voir modifié indépendamment l'un de l'autre. En programmation objet, l'héritage se trouve utilisé lorsqu'une abstraction peut posséder diverses implémentations. Le grand problème de cette approche concerne la manque de souplesse d'un système car l'héritage lie l'implémentation de façon définitive. Dès lors, la modification des abstractions conduit à de profonds changements au sein des implémentations. Ceci est particulièrement inacceptable lors de développements professionnels régis par des contraintes de coûts et de temps très strictes. Éviter de telles situations caractérise la motivation du motif de conception Pont.

Prenons le cas d'un logiciel capable de dresser la liste des produits offerts par une société ainsi qu'un état des stocks. La première liste doit afficher simplement le nom des produits et pourra être utile aux clients de la société. La seconde liste doit quant à elle non seulement afficher le nom des produits, mais également la quantité disponible en stock, son usage sera interne à l'entreprise. L'affichage de ces deux listes de données nécessite deux composants graphiques différents : une simple liste (comme celles employées pour illustrer le motif précédent) et un tableau. Ces deux composants travaillent sur les mêmes données.

Une implémentation simple de ces composants ne poserait aucun problème particulier. Néanmoins, les ennuis commencent lorsque l'on désire trier les données automatiquement à l'affichage. Nous ne possédons que deux implémentations, certes, mais si nous en avions bien plus ? Allons-nous implémenter un tri dans chacune d'entre elles ? Quoi qu'il en soit, nous pouvons créer un pont afin d'apporter une réponse efficace au problème.



Schéma 1. Le motif Pont

La figure une illustre ce qu'un pont achèvera comme travail à notre place. Le listing numéro deux propose un exemple de pont en Java. Notre pont décrit ici un panneau graphique défilant au sein duquel nous plaçons la liste ou le tableau. Le client doit fournir au pont le type de représentation souhaité, ici LIST ou TABLE. La transformation nécessaire à la visualisation de données ne demande ici que le simple ajout de la ligne suivante au début du constructeur :

ArrayList sorted = sortArrayList(data);
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
L'argument data des deux appels de méthodes suivants devient donc sorted. Le gain de temps fournit par l'utilisation de ce motif se veut très important. De plus, si vous décidez de ne plus utiliser de tables ni de listes, vous pourrez simplement changer le code du pont et éviterez de créer de nouvelles structures complexes. Pour terminer, vous pourrez faire aisément évoluer l'implémentation (les composants graphiques ici) sans pour autant modifier le code client ni le pont.


 Listings

Listing 1

class PyQTList(MultiList, QHBox):
  def __init__(self, parent):
    QHBox.__init__(self, parent)
    self.list = QListBox(self)

  def currentItem(self):
    return self.list.currentItem()

  def insertListOfStrings(self, list, pos = -1):
    self.list.insertStrList(list, -1)

  def removeItemAt(self, pos):
    self.list.removeItem(pos)

  def getSelectedItems(self):
    return [str(self.list.text(self.list.currentItem()))]
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Listing 2

public class ProductsBridge extends JScrollPane
{
  public static int LIST = 1;
  public static int TABLE = 2;

  public ProductsBridge(ArrayList data, int type)
  {
     if (type == LIST)  createList(data);
     else if (type == TABLE) createTable(data);
  }
}
       
      
JextCopier dans Jext

 Exemple complet

#!/usr/bin/env python

import sys
from qt import *

class MultiList:
    def currentItem(self): pass
    def insertListOfStrings(self, list, pos = -1): pass
    def removeItemAt(self, pos): pass
    def getSelectedItems(self): return None

class PyQTList(MultiList, QHBox):
    def __init__(self, parent):
        QHBox.__init__(self, parent)
        self.list = QListBox(self)

    def currentItem(self):
        return self.list.currentItem()

    def insertListOfStrings(self, list, pos = -1):
        self.list.insertStrList(list, -1)

    def removeItemAt(self, pos):
        self.list.removeItem(pos)

    def getSelectedItems(self):
        return [str(self.list.text(self.list.currentItem()))]

# définition d'une nouvelle classe
# héritant du widget QHBox
class MyWidget(QVBox):
    def __init__(self, parent = None, name = None):
        QVBox.__init__(self, parent, name)

        # nous positionnons la fenêtre et lui
        # attribuons une taille spécifique
        self.setGeometry(4, 24, 200, 200)

        lists = QHBox(self)

        # liste 1
        self.list1 = PyQTList(lists)
        self.list1.insertListOfStrings(['Marina', 'Laure', 'Elodie'])

        # liste 2
        self.list2 = PyQTList(lists)
        self.list2.insertListOfStrings(['Charlotte', 'Marie'])

        # boutons
        buttons = QHBox(self)
        toRight = QPushButton("-->", buttons)
        self.connect(toRight, SIGNAL("clicked()"), self.toRight)
        toLeft = QPushButton("<--", buttons)
        self.connect(toLeft, SIGNAL("clicked()"), self.toLeft)

    def toRight(self):
        self.list2.insertListOfStrings(self.list1.getSelectedItems())
        self.list1.removeItemAt(self.list1.currentItem())

    def toLeft(self):
        self.list1.insertListOfStrings(self.list2.getSelectedItems())
        self.list2.removeItemAt(self.list2.currentItem())

# création de l'application
app = QApplication(sys.argv)

# création du widget principal
wgHello = MyWidget()
app.setMainWidget(wgHello)

# exécution de l'application
wgHello.show()
app.exec_loop()

# fin du script adaptateur.py
       
      
JextCopier dans Jext | Jext | Plugin Codegeek


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