IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java

  Chapitre 13 - Création de fenêtres & d'Applets

pages : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 

Jusqu'ici, dans ce livre, nous avons vu que Java permet de créer des morceaux de code réutilisables. L'unité de code la plus réutilisable est la classe, car elle contient un ensemble cohérent de caractéristiques (champs) et comportements (méthodes) qui peuvent être réutilisées soit directement par combinaison, soit par héritage.

L'héritage et le polymorphisme sont des éléments essentiels de la programmation orientée objet, mais dans la majorité des cas, lorsqu'on bâtit une application, en fait on désire disposer de composants qui font exactement ce qu'on veut. On aimerait placer ces éléments dans notre conception comme l'ingénieur électronicien qui assemble des puces sur un circuit. On sent bien qu'il devrait y avoir une façon d'accélérer ce style de programmation modulaire.

La programmation visuelle est devenue très populaire d'abord avec le Visual Basic (VB) de Microsoft, ensuite avec une seconde génération d'outils, avec Delphi de Borland (l'inspiration principale de la conception des JavaBeans). Avec ces outils de programmation, les composants sont représentés visuellement, ce qui est logique car ils affichent d'habitude un composant visuel tel qu'un bouton ou un champ de texte. La représentation visuelle est en fait souvent exactement l'aspect du composant lorsque le programme tournera. Une partie du processus de programmation visuelle consiste à faire glisser un composant d'une palette pour le déposer dans un formulaire. Pendant qu'on fait cette opération, l'outil de construction d'applications génère du code, et ce code entraînera la création du composant lors de l'exécution du programme.

Le simple fait de déposer des composants dans un formulaire ne suffit généralement pas à compléter le programme. Il faut souvent modifier les caractéristiques d'un composant, telles que sa couleur, son texte, à quelle base de données il est connecté, et cetera. Des caractéristiques pouvant être modifiées au moment de la conception s'appellent des propriétés [properties]. On peut manipuler les propriétés du composant dans l'outil de construction d'applications, et ces données de configuration sont sauvegardées lors de la construction du programme, de sorte qu'elles puissent être régénérées lors de son exécution.

Vous êtes probablement maintenant habitués à l'idée qu'un objet est plus que des caractéristiques ; c'est aussi un ensemble de comportements. A la conception, les comportements d'un composant visuel sont partiellement représentés par des événements [events], signifiant : «ceci peut arriver à ce composant». En général on décide de ce qui se passera lorsqu'un événement apparaît en liant du code à cet événement.

C'est ici que se trouve le point critique du sujet : l'outil de construction d'applications utilise la réflexion pour interroger dynamiquement le composant et découvrir quelles propriétés et événements le composant accepte. Une fois connues, il peut afficher ces propriétés et en permettre la modification (tout en sauvegardant l'état lors de la construction du programme), et afficher également les événements. En général, on double-clique sur un événement et l'outil crée la structure du code relié à cet événement. Tout ce qu'il reste à faire est d'écrire le code qui s'exécute lorsque cet événement arrive.

Tout ceci fait qu'une bonne partie du travail est faite par l'outil de construction d'applications. On peut alors se concentrer sur l'aspect du programme et ce qu'il est supposé faire, et s'appuyer sur l'outil pour s'occuper du détail des connexions. La raison pour laquelle les outils de programmation visuels on autant de succès est qu'ils accélèrent fortement le processus de construction d'une application, l'interface utilisateur à coup sûr, mais également d'autres parties de l'application.

Qu'est-ce qu'un Bean ?

Une fois la poussière retombée, un composant est uniquement un bloc de code, normalement intégré dans une classe. La clé du système est la capacité du constructeur d'applications de découvrir les propriétés et événements de ce composant. Pour créer un composant VB, le programmeur devait écrire un bout de code assez compliqué, en suivant certaines conventions pour exposer les propriétés et événements. Delphi est un outil de programmation visuelle de seconde génération, pour lequel il est beaucoup plus facile de créer un composant visuel. Java de son côté a porté la création de composants visuels à son état le plus avancé, avec les JavaBeans, car un Bean est tout simplement une classe. Il n'y a pas besoin d'écrire de code supplémentaire ou d'utiliser des extensions particulières du langage pour transformer quelque chose en Bean. La seule chose à faire, en fait, est de modifier légèrement la façon de nommer les méthodes. C'est le nom de la méthode qui dit au constructeur d'applications s'il s'agit d'une propriété, d'un événement, ou simplement une méthode ordinaire.

Dans la documentation Java, cette convention de nommage est par erreur désignée comme un modèle de conception [design pattern]. Ceci est maladroit, car les modèles de conception (voir Thinking in Patterns with Java, téléchargeable à www.BruceEckel.com) sont suffisamment difficiles à comprendre sans ajouter ce genre de confusions. Ce n'est pas un modèle de conception, c'est uniquement une convention de nommage assez simple :

  1. Pour une propriété nommée xxx, on crée deux méthodes : getXxx() et setXxx(). Remarquons que la première lettre après get ou set est transformée automatiquement en minuscule pour obtenir le nom de la propriété. Le type fourni par la méthode get est le même que le type de l'argument de la méthode set. Le nom de la propriété et le type pour les méthodes get et set ne sont pas liés.
  2. Pour une propriété de type boolean, on peut utiliser les méthodes get et set comme ci-dessus, ou utiliser is au lieu de get.
  3. Les méthodes ordinaires du Bean ne suivent pas la convention de nommage ci-dessus, mais elles sont public.
  4. Pour les événements, on utilise la technique Swing du listener. C'est exactement la même chose que ce qu'on a déjà vu : addFooBarListener(FooBarListener) et removeFooBarListener(FooBarListener) pour gérer un FooBarEvent. La plupart du temps, les événements intégrés satisfont les besoins, mais on peut créer ses propres événements et interfaces listeners.

Le point 1 ci-dessus répond à la question que vous vous êtes peut-être posée en comparant un ancien et un nouveau code : un certain nombre de méthodes ont subi de petits changements de noms, apparemment sans raison. On voit maintenant que la plupart de ces changements avaient pour but de s'adapter aux conventions de nommage get et set de manière à transformer les composants en Beans.

On peut utiliser ces règles pour créer un Bean simple :

//: frogbean:Frog.java
// Un JavaBean trivial.
package frogbean;
import java.awt.*;
import java.awt.event.*;

class Spots {}

public class Frog {
  private int jumps;
  private Color color;
  private Spots spots;
  private boolean jmpr;
  public int getJumps() { return jumps; }
  public void setJumps(int newJumps) {
    jumps = newJumps;
  }
  public Color getColor() { return color; }
  public void setColor(Color newColor) {
    color = newColor;
  }
  public Spots getSpots() { return spots; }
  public void setSpots(Spots newSpots) {
    spots = newSpots;
  }
  public boolean isJumper() { return jmpr; }
  public void setJumper(boolean j) { jmpr = j; }
  public void addActionListener(
      ActionListener l) {
    //...
  }
  public void removeActionListener(
      ActionListener l) {
    // ...
  }
  public void addKeyListener(KeyListener l) {
    // ...
  }
  public void removeKeyListener(KeyListener l) {
    // ...
  }
  // Une méthode public "ordinaire" :
  public void croak() {
    System.out.println("Ribbet!");
  }
} ///:~

Tout d'abord, on voit qu'il s'agit d'une simple classe. En général, tous les champs seront private, et accessibles uniquement à l'aide des méthodes. En suivant la convention de nommage, les propriétés sont jumps, color, spots et jumper (remarquons le passage à la minuscule pour la première lettre du nom de la propriété). Bien que le nom de l'identificateur interne soit le même que le nom de la propriété dans les trois premiers cas, dans jumper on peut voir que le nom de la propriété n'oblige pas à utiliser un identificateur particulier pour les variables internes (ou même, en fait, d'avoir des variable internes pour cette propriété).

Les événements gérés par ce Bean sont ActionEvent et KeyEvent, basés sur le nom des méthodes add et remove pour le listener associé. Enfin on remarquera que la méthode ordinaire croak() fait toujours partie du Bean simplement parce qu'il s'agit d'une méthode public, et non parce qu'elle se conforme à une quelconque convention de nommage.

Extraction des informations sur les Beans [BeanInfo] à l'aide de l'introspecteur [Introspector]

L'un des points critiques du système des Beans est le moment où on fait glisser un bean d'une palette pour le déposer dans un formulaire. L'outil de construction d'applications doit être capable de créer le Bean (il y arrive s'il existe un constructeur par défaut) et, sans accéder au code source du Bean, extraire toutes les informations nécessaires à la création de la feuille de propriétés et de traitement d'événements.

Une partie de la solution est déjà évidente depuis la fin du Chapitre 12 : la réflexion Java permet de découvrir toutes les méthodes d'une classe anonyme. Ceci est parfait pour résoudre le problème des Beans, sans avoir accès à des mots clés du langage spéciaux, comme ceux utilisés dans d'autres langages de programmation visuelle. En fait, une des raisons principales d'inclure la réflexion dans Java était de permettre les Beans (bien que la réflexion serve aussi à la sérialisation des objets et à l'invocation de méthodes à distance [RMI : remote method invocation]). On pourrait donc s'attendre à ce qu'un outil de construction d'applications doive appliquer la réflexion à chaque Bean et à fureter dans ses méthodes pour trouver les propriétés et événements de ce Bean.

Ceci serait certainement possible, mais les concepteurs du langage Java voulaient fournir un outil standard, non seulement pour rendre les Beans plus faciles à utiliser, mais aussi pour fournir une plate-forme standard pour la création de Beans plus complexes. Cet outil est la classe Introspector, la méthode la plus importante de cette classe est le static getBeanInfo(). On passe la référence d'une Class à cette méthode , elle l'interroge complètement et retourne un objet BeanInfo qu'on peut disséquer pour trouver les propriétés, méthodes et événements.

Vous n'aurez probablement pas à vous préoccuper de tout ceci, vous utiliserez probablement la plupart du temps des beans prêts à l'emploi, et vous n'aurez pas besoin de connaître toute la magie qui se cache là-dessous. Vous ferez simplement glisser vos Beans dans des formulaires, vous en configurerez les propriétés et vous écrirez des traitements pour les événements qui vous intéressent. Toutefois, c'est un exercice intéressant et pédagogique d'utiliser l'Introspector pour afficher les informations sur un Bean, et voici donc un outil qui le fait :

//: c13:BeanDumper.java
// Introspection d'un Bean.
// <applet code=BeanDumper width=600 height=500>
// </applet>
import java.beans.*;
import java.lang.reflect.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class BeanDumper extends JApplet {
  JTextField query =
    new JTextField(20);
  JTextArea results = new JTextArea();
  public void prt(String s) {
    results.append(s + "\n");
  }
  public void dump(Class bean){
    results.setText("");
    BeanInfo bi = null;
    try {
      bi = Introspector.getBeanInfo(
        bean, java.lang.Object.class);
    } catch(IntrospectionException e) {
      prt("Couldn't introspect " +
        bean.getName());
      return;
    }
    PropertyDescriptor[] properties =
      bi.getPropertyDescriptors();
    for(int i = 0; i       Class p = properties[i].getPropertyType();
      prt("Property type:\n  " + p.getName() +
        "Property name:\n  " +
        properties[i].getName());
      Method readMethod =
        properties[i].getReadMethod();
      if(readMethod != null)
        prt("Read method:\n  " + readMethod);
      Method writeMethod =
        properties[i].getWriteMethod();
      if(writeMethod != null)
        prt("Write method:\n  " + writeMethod);
      prt("====================");
    }
    prt("Public methods:");
    MethodDescriptor[] methods =      bi.getMethodDescriptors();
    for(int i = 0; i       prt(methods[i].getMethod().toString());
    prt("======================");
    prt("Event support:");
    EventSetDescriptor[] events =
      bi.getEventSetDescriptors();
    for(int i = 0; i       prt("Listener type:\n  " +
        events[i].getListenerType().getName());
      Method[] lm =
        events[i].getListenerMethods();
      for(int j = 0; j         prt("Listener method:\n  " +
          lm[j].getName());
      MethodDescriptor[] lmd =
        events[i].getListenerMethodDescriptors();
      for(int j = 0; j         prt("Method descriptor:\n  " +
          lmd[j].getMethod());
      Method addListener =
        events[i].getAddListenerMethod();
      prt("Add Listener Method:\n  " +
          addListener);
      Method removeListener =        events[i].getRemoveListenerMethod();
      prt("Remove Listener Method:\n  " +
        removeListener);
      prt("====================");
    }
  }
  class Dumper implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      String name = query.getText();
      Class c = null;
      try {
        c = Class.forName(name);
      } catch(ClassNotFoundException ex) {
        results.setText("Couldn't find " + name);
        return;
      }
      dump(c);
    }
  }      
  public void init() {
    Container cp = getContentPane();
    JPanel p = new JPanel();
    p.setLayout(new FlowLayout());
    p.add(new JLabel("Qualified bean name:"));
    p.add(query);
    cp.add(BorderLayout.NORTH, p);
    cp.add(new JScrollPane(results));
    Dumper dmpr = new Dumper();
    query.addActionListener(dmpr);
    query.setText("frogbean.Frog");
    // Force evaluation
    dmpr.actionPerformed(
      new ActionEvent(dmpr, 0, ""));
  }
  public static void main(String[] args) {
    Console.run(new BeanDumper(), 600, 500);
  }
} ///:~

BeanDumper.dump() est la méthode qui fait tout le travail. Il essaie d'abord de créer un objet BeanInfo, et en cas de succès il appelle les méthodes de BeanInfo qui fournissent les informations sur les propriétés, méthodes et événements. Dans Introspector.getBeanInfo(), on voit qu'il y a un second argument. Celui-ci dit à l'Introspector où s'arrêter dans la hiérarchie d'héritage. Ici, il s'arrête avant d'analyser toutes les méthodes d'Object, parce qu'elles ne nous intéressent pas.

Pour les propriétés, getPropertyDescriptors() renvoie un tableau de PropertyDescriptors. Pour chaque PropertyDescriptor, on peut appeler getPropertyType() pour connaître la classe d'un objet passé par les méthodes de propriétés. Ensuite, on peut obtenir le nom de chaque propriété (issu du nom des méthodes) à l'aide de getName(), la méthode pour la lire à l'aide de getReadMethod(), et la méthode pour la modifier à l'aide de getWriteMethod(). Ces deux dernières méthodes retournent un objet Method qui peut être utilisé pour appeler la méthode correspondante de l'objet (ceci fait partie de la réflexion).

Pour les méthodes public (y compris les méthodes des propriétés), getMethodDescriptors() renvoie un tableau de MethodDescriptors. Pour chacun de ces descripteurs, on peut obtenir l'objet Method associé, et imprimer son nom.

Pour les événements, getEventSetDescriptors() renvoie un tableau de (que pourrait-il renvoyer d'autre ?) EventSetDescriptors. Chacun de ces descripteurs peut être utilisé pour obtenir la classe du listener, les méthodes de cette classe listener, et les méthodes pour ajouter et enlever ce listener. Le programme BeanDumper imprime toutes ces informations.

Au démarrage, le programme force l'évaluation de frogbean.Frog. La sortie, après suppression de détails inutiles ici, est :

class name: Frog
Property type:
  Color
Property name:
  color
Read method:
  public Color getColor()
Write method:
  public void setColor(Color)
====================Property type:
  Spots
Property name:
  spots
Read method:
  public Spots getSpots()
Write method:
  public void setSpots(Spots)
====================Property type:
  boolean
Property name:
  jumper
Read method:
  public boolean isJumper()
Write method:
  public void setJumper(boolean)
====================Property type:
  int
Property name:
  jumps
Read method:
  public int getJumps()
Write method:
  public void setJumps(int)
====================Public methods:
public void setJumps(int)
public void croak()
public void removeActionListener(ActionListener)
public void addActionListener(ActionListener)
public int getJumps()
public void setColor(Color)
public void setSpots(Spots)
public void setJumper(boolean)
public boolean isJumper()
public void addKeyListener(KeyListener)
public Color getColor()
public void removeKeyListener(KeyListener)
public Spots getSpots()
======================Event support:
Listener type:
  KeyListener
Listener method:
  keyTyped
Listener method:
  keyPressed
Listener method:
  keyReleased
Method descriptor:
  public void keyTyped(KeyEvent)
Method descriptor:
  public void keyPressed(KeyEvent)
Method descriptor:
  public void keyReleased(KeyEvent)
Add Listener Method:
  public void addKeyListener(KeyListener)
Remove Listener Method:
  public void removeKeyListener(KeyListener)
====================Listener type:
  ActionListener
Listener method:
  actionPerformed
Method descriptor:
  public void actionPerformed(ActionEvent)
Add Listener Method:
  public void addActionListener(ActionListener)
Remove Listener Method:
  public void removeActionListener(ActionListener)
====================

Ce livre a été écrit par Bruce Eckel ( télécharger la version anglaise : Thinking in java )
Ce chapitre a été traduit par P. Boite ( groupe de traduction )
télécharger la version francaise (PDF) | Commandez le livre en version anglaise (amazon) | télécharger la version anglaise
pages : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java