P
'
t
i
t
e
C
h
a
t
t
e
 
spacer~ NORMAL IS JUST A SETTING ON THE DRYER Articles | Connexion
 
~Prototype, façade et commande

 Présentation

Nous allons cette fois-ci découvrir notamment le principe de clonage des objets en Java. Le premier motif de conception que nous allons explorer s'intitule le motif Prototype. Celui-ci prend place au sein du premier groupe de motifs, le groupe des motifs créateurs.
 Sommaire


 Le motif Prototype

Ce motif se voit employé dès lors que la création d'une instance d'une classe donnée requiert énormément de temps ou se révèle compliquée pour une raison ou pour une autre. Pour s'affranchir de cet écueil, le programmeur peut se reposer sur la notion de clone. Un clone incarne une instance d'une classe donnée, copie exacte d'une autre instance.

En général, ce modèle de conception dévoile son potentiel lorsque vous nécessitez l'emploi de classes différant seulement par le type de processus réalisé. Ainsi, une classe DenaryConverter désignant un convertisseur de nombre décimal vers une base numérique quelconque constitue un cas typique d'usage du motif Prototype.

La situation d'une large base de données dont la représentation se voit triée selon un critère donné incarne un autre exemple intéressant de mise en ouvre du motif considéré. Soit une liste d'élèves triés selon les critères moyenne et âge. Nous pourrions avoir besoin d'une seconde représentation triée par nom uniquement. En ce cas, le clonage des éléments de la liste se révèle plus adéquat que la recréation de celle-ci. Le clonage des données permet également de conserver notre liste originale intacte.


 Le clown âgé

La plateforme Java offre une méthode extrêmement simple pour favoriser le clonage d'objets. La classe mère Object comprend en effet une méthode intitulée clone(). Voici la signature de cette méthode dans la classe Object :

protected Object clone() throws CloneNotSupportedException
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
L'indicateur de visibilité protected ne convient absolument pas dans le cadre du motif Prototype. Nous devons nous trouver en mesure de créer un clone de l'objet depuis toute classe externe. C'est pourquoi nous surchargerons cette méthode afin de la rendre publique. Notre surcharge éliminera également la clause d'émission d'exception afin d'alléger les appels au clonage.

En Java, tout objet possède la méthode clone(). Toutefois, celle-ci ne peut se voir exécutée avec succès sur un objet quelconque. Lorsque l'objet considéré n'implémente pas l'interface java.lang.Cloneable, une exception de type CloneNotSupportedException se voit rejetée. Le listing numéro un vous propose l'implémentation d'une classe clonable. Les instructions indispensables à l'opération de clonage sont les suivantes :

A original = new A();
A clone = (A) original.clone();
       
      
JextCopier dans Jext
Remarquez que la méthode clone() retourne une instance d'Object, imposant donc un transtypage explicite. En attribuant à la classe A quelques attributs, vous constaterez que leurs valeurs se trouvent conservées dans le clone. Néanmoins, une copie d'instance effectuée de la sorte incarne ce que la documentation Java désigne sous le terme de "shallow copy".


 Les copies "shallow" et "deep"

L'expression "shallow copy" dénonce une copie incomplète. Si toutes les valeurs des attributs se voient bel et bien conservées, il s'agit de simples assignations par références. Ainsi, une instance a de la classe A et sa copie b partageront la même instance de Vector si la classe propose comme attribut un Vector. Considérons l'ajout des lignes suivantes à notre classe A :

public Vector v;
public A(Vector v)
{
  this.v = v;
}
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Prenons à présent quelques lignes mettant en avant le problème de la "shallow copy" :

A a = new A(new Vector(10));
A b = (A) a.clone();
b.addElement("BOUM ! V'LA L'REMOULEUR !");
       
      
JextCopier dans Jext
Lors de la création de l'instance a, nous proposons un nouveau Vector, entièrement vide. Nous clonons ensuite l'instance a. Nos deux instances de A, a et b, contiennent un attribut v désignant un vecteur d'une capacité de 10 éléments et de taille nulle. La dernière ligne provoque l'addition d'un élément au cour du Vector de l'instance b. Toutefois, si vous examinez le résultat produit par l'invocation de a.v.size() ou a.v.elementAt(0) vous constaterez que la chaîne de caractères ajoutée au vecteur de b se trouve également au sein du vecteur de a ! Les instances a et b de la classe A partagent donc les mêmes ressources. Nous devons pour remédier à cela effectuer une "deep copy".

Une "deep copy" consiste à créer une copie exacte de l'objet, en clonant également ses attributs. Pour parvenir à cet objectif, nous employons le principe de la sérialisation d'objets. La sérialisation consiste en une méthode de sauvegarde d'un objet sous forme d'une succession d'octets. Cette méthode fonctionne également dans l'autre sens : la lecture d'une succession d'octets peut permettre la création d'une instance valide d'une classe donnée. Le listing deux offre un exemple de "deep copy" faisant appel au principe de la sérialisation. Dans notre exemple, l'instance courante de A s'avère transformée en un tableau d'octets, puis immédiatement clonée en reconstituant une instance de A grâce à ce même tableau.

Au final, l'objet renvoyé constitue une copie parfaitement indépendante. Vous pouvez employer le même test que précédemment pour constater les bienfaits de la "deep copy". N'oubliez pas que pour sérialiser une instance d'une classe donnée, cette dernière doit implémenter l'interface java.lang.Serializable. Réfléchissez tout de même avant d'appliquer le principe de la "deep copy" car son utilisation ne se voit pas toujours justifiée. Sa consommation de ressources doit impérativement vous inciter à l'utiliser avec parcimonie.



Le clonage, plus simple et moins dangereux en Java.


 Les difficultés d'implémentation

Le motif de conception Prototype ouvre l'accès vers de nombreuses perspectives intéressantes telles que la spécification de nouveaux objets en cours d'exécution sans pour autant rédiger de nouvelles classes. Malheureusement, les principes des "shallow" et "deep copy" instaurent quelques limitations. Premièrement, vous ne possédez pas forcément un accès public à de telles méthodes sur des classes existantes. Ensuite, la "deep copy" ne fonctionnera pas si tous les attributs de l'objet cloné ne peuvent subir une sérialisation. Pour terminer, les classes formant des références circulaires avec d'autres ne pourront se voir clonées. Le dernier point important concernant le Prototype se veut relatif à l'accès aux attributs des classes à cloner. Vous devez pouvoir accéder aux attributs importants afin de modifier leurs valeurs après clonage. Cela implique bien souvent la rédaction d'accesseurs supplémentaires (méthodes de type get/set).

Par mesure exceptionnelle, nous vous encourageons à implémenter vous-mêmes un exemple d'emploi du motif de conception Prototype. Rappelez-vous que sa mise en place consiste au simple clonage d'objets et à la modification des clones pour offrir des fonctionnalités dérivées. Vous trouverez dans le zip en annexe le code source de notre fameuse classe A qui pourra faire usage de support pour ce travail.



Le motif Prototype repose sur le clonage des objets.


 Le motif Façade

Le motif Façade appartient au groupe de motifs structurels. De manière générale, l'évolution et le développement d'un programme impliquent l'accroissement de sa complexité. L'implication des motifs de conception dans la réalisation d'un logiciel participe bien souvent à la génération de nombreuses classes supplémentaires. Pour pallier cette complexité, les développeurs rédigent usuellement des sous-systèmes constituant des ensembles de classes puissants et flexibles. Toutefois, leur emploi se caractérise lui-même par une forte complexité due à l'éclatement des fonctionnalités. Afin d'offrir une interface unifiée à un sous-système, les concepteurs se doivent d'opter pour le Design Pattern nommé Façade.

Une Façade consiste en une simple interface de programmation destinée à fournir une utilisation par défaut du sous-système. La principale caractéristique de la Façade réside dans sa forte indépendance vis-à-vis du sous-système. Les classes inhérentes au système ne connaissent pas l'existence de cette Façade et n'y font jamais référence. De fait, l'utilisateur du sous-système ne se voit pas bridé par l'existence de cette Façade. Nous devons nous voir octroyer la possibilité de la contourner pour employer directement les interfaces mêmes du sous-système.

Un exemple définitivement trivial de système pouvant bénéficier d'une Façade concerne l'accès aux bases de données. Notre exemple emploie le langage C# et propose un code source affichant le contenu d'une table qui est au sein d'une base de données Access locale. Pour parvenir à ce résultat, le programme doit en premier lieu effectuer une jonction vers la base de donnée par l'entremise du driver approprié. Une fois la connexion effectuée, nous devons mettre en place une requête que nous fournirons à un adaptateur. Le rôle de ce dernier consistera à fournir les résultats de la requête au sein d'un ensemble de données (un DataSet). Vous trouverez trace de ces pérégrinations dans le listing numéro trois.

La gestion de ce sous-système (composé de l'interface de connexion, de l'interface de requêtes, etc.) nécessite la mise en ouvre de plusieurs classes fort différentes. Il en résulte un code relativement indigeste et plutôt rigide. Afin de simplifier l'utilisation des accès aux bases de données, nous optons pour la création d'une Façade. Le rôle exact de la Façade consiste à encapsuler le code que nous venons de voir pour le rendre plus accessible. L'interface contenue dans le listing numéro quatre présente une généralisation de l'exemple que nous venons d'aborder.

Grâce à cette façade nous pourrons alors rédiger les instructions suivantes :

Facade.Databse db = new Facade.Database("Microsoft.Jet.OLEDB.4.0");
db.Open("Authors.mdb");
DataSet ds = db.ExecuteQuery("SELECT * FROM Authors");
db.Close();
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Le code source que nous obtenons de la sorte se révèle bien plus lisible que le précédent. Toutefois, l'application de notre Façade implique une spécialisation du code. Gardez donc bien à l'esprit lors de la conception d'une Façade que celle-ci doit correspondre au cas le plus courant de l'utilisation de votre sous-système.



Le motif Façade ouvre un accès simplifié à un sous-système.


 Le motif Commande

Membre du groupe de motifs de comportement, le motif Commande s'apparente par certains aspects au motif Chaîne de responsabilité. Ce dernier propage une requête d'action le long d'une chaîne de classes. Le motif Commande lui ne propage une requête que vers une seule et unique classe. Son intérêt réside dans la possibilité de proposer une interface publique de résolution d'une action sans que l'utilisateur de la commande ne sache comment la requête se verra traitée. Cela permet de modifier le comportement de l'action sans modifier pour autant le programme appelant cette action. Voyons une application concrète.

En Java, lorsque l'utilisateur active un élément de menu ou un bouton, un événement de type ActionEvent se voit transmis à l'écouteur symbolisé par la méthode actionPerformed(). Un cas typique de traitement des événements ressemble à ceci :

public void actionPerformed(ActionEvent evt)
{
  Object o = evt.getSource();
  if (o == buttonOpen)
    doOpen();
  else if (o == buttonClose)
    doClose();
  // .
}
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Ce type de traitement relève plus de la programmation procédurale que de la programmation orientée objet. De plus, modifier le comportement d'une action, par exemple un clic sur le bouton buttonOpen, nécessite des modifications dans un code déjà lourd. Procédons donc à la mise en ouvre du motif Commande :

public void actionPerformed(ActionEvent evt)
{
  Command cmd = (Command) evt.getSource();
  cmd.execute();
}
       
      
JextCopier dans Jext
L'interface Command contiendra toujours au minimum la méthode execute(). Nous assumons ici que toute source d'événement implémente cette interface. Traitons le cas du bouton buttonOpen :

class OpenButton extends JButton implements Command
{
  public void execute()
  {
    // ouverture
  }
}
       
      
JextCopier dans Jext
Ce motif entraîne dans la plupart des cas une prolifération de petites classes de traitement. Dans le cas présenté ici, nous pouvons imaginer que la méthode execute() de OpenButton nécessite l'accès aux propriétés de sa fenêtre parente. Nous éviterons de créer la classe OpenButton en tant que classe interne de la fenêtre en créant un constructeur de ce type : OpenButton(String label, JTextArea area). L'ensemble de ces considérations présume que tous les composants traités emploient un seul et unique écouteur d'événements. Une seconde approche existe et se base sur la réalisation d'un écouteur par composant.

class OpenAction implements ActionListener
{
  public void actionPerformed(ActionEvent evt)
  {
    // equivalent à execute()
  }
}
       
      
JextCopier dans Jext
La méthode actionPerformed() joue ici le rôle de la méthode execute() de l'interface Command. Nous mettons également en place un motif Commande. Les programmeurs Java couplent bien souvent ce motif et la création d'un écouteur par composant avec la notion de classes anonymes internes :

buttonOpen.addActionListener(new ActionListener()
{
  public void actionPerformed(ActionEvent evt)
  {
    // execute()
  }
});
       
      
JextCopier dans Jext
La description de ce dernier motif de conception, que vous avez déjà sûrement employé si vous programmez en Java, devrait vous prouver que les Design Patterns s'avèrent bien souvent employés par "inadvertance" car leur implémentation s'avère naturelle.


 Annexes

Listing 1 :

class A implements Cloneable
{
  public Object clone()
  {
    try
    {
      return super.clone();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }
}
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Listing 2 :

public Object deepClone()
{
  try
  {
    ByteArrayOutputStream b = new ByteArrayOutputStream();
    ObjectOutputStream out = new ObjectOutputStream(b);
    out.writeObject(this);
    out.close();

    ByteArrayInputStream clone = new ByteArrayInputStream(b.toByteArray());
    ObjectInputStream in = new ObjectInputStream(clone);
    return in.readObject();
  } catch (Exception e) {
    e.printStackTrace();
  }
  return null;
}
       
      
JextCopier dans Jext
Listing 3 :

string strAccessConn   = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Authors.mdb";
string strAccessSelect = "SELECT * FROM Authors";

DataSet myDataSet = new DataSet();
myDataSet.Tables.Add("Authors");

OleDbConnection  myAccessConn    = new OleDbConnection(strAccessConn);
OleDbCommand     myAccessCommand = new OleDbCommand(strAccessSelect,myAccessConn);
OleDbDataAdapter myDataAdapter   = new OleDbDataAdapter(myAccessCommand);

myAccessConn.Open();

try
{
  myDataAdapter.Fill(myDataSet, "Authors");
} finally {
  myAccessConn.Close();
}
       
      
JextCopier dans Jext
Listing 4 :

namespace Facade {
  class Database
  {
    public Database(string driver) { }
    public void Open(string source);
    public string[] GetTableNames();
    public DataSet ExecuteQuery(string query);
    // .
  }
}
       
      
JextCopier dans Jext



Un des nombreux ouvrages traitant des motifs de conception.



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