Copyright (c) Martin Lafaix, 1997. Tous droits réservés. ©
Home | NetRexx | Approfondissements

NetRexx - Approfondissements

Dans les deux premiers articles de cette série, nous nous étions contenté d'une approche simple de NetRexx, d'abord en en offrant une présentation rapide, puis en réalisant une application somme toute ordinaire, afin de montrer comment étaient liés NetRexx et Java. Cette fois ci, nous allons aborder quelques fonctionnalités plus évoluées de NetRexx.

Le sujet de cet article étant NetRexx et pas la boîte à outils Java, celle-ci est censée être connue.

Ce que nous allons faire

Avec l'aide de quelques exemples, nous allons évoquer certaines fonctionnalités intéressantes de NetRexx : les tableaux, les classes adaptatrices et les attributs indirects. Certaines d'entres elles ont été introduites avec la version 1.1 et suivantes de NetRexx, et il est donc conseillé d'utiliser cette version[1].

Les tableaux

Dans les articles précédents, nous n'avions pas parlé des tableaux, même s'ils étaient présents par exemple dans la déclaration de la méthode main(), celle-ci nécessitant comme argument un tableau de String.

Déclaration

La déclaration d'un tableau s'indique en faisant directement suivre le nom d'une classe de crochets indiquant le nombre de dimensions. Par exemple :

   foo = String[]
   table = int[,]

déclare foo comme étant un tableau de String et table un tableau à deux dimensions d'entiers (autrement dit, un tableau de tableaux d'entiers).

Les déclarations sont utilisées lors de la spécification des attributs, des paramètres d'une méthode et également lorsque la première affectation à une variable locale ne permet pas de déterminer le type souhaité.

Définition

Les définitions ressemblent aux déclarations, si ce n'est que la dimension est précisée. Par exemple :

   foo = String[10]
   table = int[10,10]

définit foo comme étant un tableau de 10 éléments de type String et table comme un tableau contenant 100 (10*10) entiers. (Il n'est pas nécessaire de déclarer un tableau avant de le définir.)

Lorsqu'un tableau est créé, ses éléments sont initialisés à une valeur par défaut (0 pour les nombres, null pour les objets).

Accès

Pour accéder à un élément d'un tableau, il suffit de faire directement suivre la variable ou l'attribut de crochets précisant l'élément souhaité.

Les indices autorisés pour les tableaux vont de 0 à nombre_d'éléments-1. Par exemple, on peut initialiser les tableaux précédemment définis avec :

   loop i = 0 to foo.length-1
     foo[i] = i
   end

   loop j = 0 to table.length-1
     loop k = 0 to table[j].length-1
       table[j,k] = j*10+k
     end
   end

length() est une méthode des objets de type tableau qui retourne le nombre d'éléments contenus dans ceux-ci. On peut donc réécrire la première ligne de l'exemple précédent comme suit : « loop i = 0 to 10-1 ». length() autorise un code plus propre.

La sixième ligne « loop k = 0 to table[j].length-1 » montre comment obtenir la seconde dimension de table. En effet, table étant un tableau de tableaux, si l'on accède à l'un de ses éléments sans préciser toutes les dimensions requises, on obtient en retour un tableau. Ainsi « table[j,k] » retourne un entier et « table[j] » un tableau d'entier.

Les tableaux littéraux

En plus des points évoqués ci-dessus, NetRexx permet l'utilisation de tableaux de littéraux. Pour ce, il suffit d'inclure entre crochets une liste de valeurs. Par exemple, on peut réécrire l'exemple précédent de la manière suivante :

   foo = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
   table = [[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9], -
            [10, 11, 12, 13, 14, 15, 16, 17, 18, 19], -
            ...
            [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]]

Rappelez-vous qu'un tableau à plusieurs dimensions est en fait un tableau de tableaux.

Ces littéraux peuvent être passés en argument à une méthode ou être utilisés en début d'une expression (en fait, partout où une variable locale ou un attribut de type tableau serait accepté) :

   say [1, 2, 3].length
   say MaClasse.maximum([12, 4, 67, 56, 42])

Un tableau littéral doit contenir au moins un élément, sans quoi il serait impossible de déterminer son type. Donc « [] » n'est pas accepté. Pour indiquer par exemple un tableau vide d'entiers, on peut utiliser « int[0] ».

Pour résumer

Voyons maintenant un petit exemple récapitulatif : la conversion d'un nombre en son équivalent en chiffres romains :

-- roman.nrx (arabic to roman conversion)

class roman

  properties constant
  equivalents = ["", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"]

  method main(arg = String[]) static
    say toroman(arg[0])

  method toroman(n) static
    if n <= 0 | n >= 4000 then signal IllegalArgumentException()
    result = ""
    loop i = 1 to n.length
      result = result.translate("xlcdm**", "ivxlcdm") || equivalents[n.substr(i,1)]
    end
    return result

Pour utiliser cet exemple, il suffit de préciser un nombre sur la ligne de commande (ce nombre doit être compris entre 1 et 3999, étant donné qu'il est impossible de représenter des nombres hors de cet intervalle en chiffres romains). Si vous souhaitez utiliser ce programme à partir d'une application, il suffit d'appeler la méthode statique toroman().

Les classes adaptatrices

L'apparition du JDK 1.1 et de son nouveau modèle de gestion des événements amène à l'emploi fréquent de « Listeners » lorsque l'on veut pouvoir réagir à telle ou telle situation.

Il arrive cependant souvent que seuls un ou deux des événements reportés par un « Listener » nous intéressent. Par exemple, si l'on souhaite être informé des actions de la souris (en utilisant un MouseListener), nous devons implémenter cinq méthodes : mouseClicked(), mouseEntered(), mouseExited(), mousePressed() et mouseReleased().

Cela peut vite devenir fastidieux si par exemple seul l'événement mouseClicked nous intéresse.

NetRexx propose une solution simple à ce problème avec les classes adaptatrices (ainsi dénommées car elles utilisent le mot clef adapter). Celles-ci ont comme caractéristique d'offrir une implémentation par défaut (et qui ne fait rien) des méthodes demandées par les interfaces, mais non implémentées. Par exemple :

-- UnEssai.nrx

class UnEssai adapter extends Frame implements MouseListener

  ...
  foo.addMouseListener(this)
  ...

  -- seule la méthode mouseClicked de l'interface MouseListener
  -- est implémentée :
  method mouseClicked(e = MouseEvent)
    ...

Comparée à la solution proposée par le langage Java, qui nécessite l'utilisation de classes imbriquées, NetRexx montre encore une fois ses qualités de lisibilité et de concision. En effet, l'équivalent Java de l'exemple précédent serait par exemple :

// UnEssai.java

class UnEssai extends Frame {

  ...
  SousClasseIndispensable uneSousClasseIndispensable = new SousClasseIndispensable();
  foo.addMouseListener(uneSousClasseIndispensable);
  ...

  // MouseAdapter offre une implémentation par défaut des méthodes
  // requises par MouseListener.  Nous n'avons donc ici qu'à
  // redéfinir mouseClicked.
  class SousClasseIndispensable extends MouseAdapter {
    public void mouseClicked(MouseEvent e) {
      ...
    }
  }
}

Et cet exemple est encore gentil puisqu'une classe adaptatrice (MouseAdapter) est présente dans la librairie standard, ce qui n'est pas toujours le cas.

Les attributs indirects

L'apparition des JavaBeans et des conventions (« Design Patterns ») associées apporte un supplément d'homogénéité et de cohérence dans la dénomination des méthodes d'accès aux attributs.

Ces conventions, qui permettent une analyse automatique des classes par les outils de développement, imposent une certaine discipline aux programmeurs. Discipline qu'il est hélas facile d'outrepasser involontairement puisqu'elle est impossible à vérifier automatiquement. Une simple faute de frappe peut ainsi rendre un attribut inaccessible aux outils automatique.

Par exemple, supposons que nous voulions créer un attribut nommé couleur et le rendre visible aux outils automatique. Le code suivant fait l'affaire :

   properties private
   couleur = Color.gray

   method getCouleur returns Color
     return couleur

   method setCouleur(c = Color)
     couleur = c

Cependant, la moindre faute ou erreur de casse dans le nom des deux méthodes définies suffit à rendre cet attribut inaccessible ou à le considérer comme n'étant accessible qu'en lecture. Les probabilités d'erreurs augmentent encore s'il s'agit d'un attribut indexé, puisqu'il faut alors définir quatre méthodes au lieu de deux.

Contrairement à nombre d'erreurs de dénomination de méthode, il n'est pas possible ici de détecter une erreur éventuelle de la part du programmeur.

NetRexx propose la notion d'attribut indirect pour remédier à ce problème et alléger le travail du programmeur en générant automatiquement les méthodes nécessaires.

L'exemple précédent peut être réécrit comme suit :

   properties indirect
   couleur = Color.gray

C'est tout. Les méthodes getCouleur() et setCouleur() sont automatiquement générées.

Dans le cas des attributs indexés (i.e., d'attributs qui sont des tableaux) les méthodes nécessaires sont également produites. Par exemple :

   properties indirect
   liste = String[]

générera les méthodes suivantes :

   method getListe returns String[]
     return liste
   method getListe($1 = int) returns String
     return liste[$1]
   method setListe($2 = String[])
     liste = $2
   method setListe($3 = int, $4 = String)
     liste[$3] = $4

Il arrive souvent que l'on veuille pouvoir réagir lors de la modification de la valeur d'un attribut, par exemple si celui-ci est représenté à l'écran. Les méthodes générées automatiquement ne peuvent bien sûr pas prendre en compte ces considérations. Pour que cette réaction à la modification d'un attribut puisse avoir lieu, il faut explicitement définir la méthode souhaitée. Par exemple, on pourrait redéfinir la méthode setCouleur() précédente :

   method setCouleur(c = Color)
     couleur = c
     this.repaint()

Si cette méthode est définie dans la classe, NetRexx ne la redéfinira pas (mais définira getCouleur() si elle n'existe pas).

Afin d'aider à la détection des erreurs de dénomination ou de signature évoquées plus haut, lorsqu'un attribut est déclarée indirect, NetRexx affiche un message d'avertissement si une méthode pouvant passer pour un accesseur[2] n'est pas publique ou si la signature d'une méthode dont le nom est celui d'un accesseur ne correspond pas à celle qui serait générée automatiquement.

Cette notion d'attribut indirect, avec les automatismes associés permet d'une part de faciliter la vie du programmeur et d'autre part de détecter un large éventail d'erreur lors de la compilation.

Conclusions

Nous venons de voir trois fonctionnalités de NetRexx. La première, les tableaux, est on ne peut plus commune (si ce n'est par la simplicité de définition des tableaux littéraux). Les deux dernières, les classes adaptatrices et les attributs indirects montrent une fois encore la simplicité et le confort que peut apporter un langage intelligemment conçu.

À bientôt pour de nouvelles aventures NetRexxiennes....


Notes:
[1] Disponible à l'endroit habituel, http://www2.hursley.ibm.com/netrexx/.
[2] Et hop, un néologisme, un ! :-) Je le préfère au terme « accédant » qui de toute façon désigne une personne, pas une méthode...

Martin Lafaix