UP | HOME

Définir un type de données : la méthode

Table of Contents

Concernant un type de données, on peut prendre deux points de vue :

En général, il existe plusieurs usages possibles et plusieurs modes de construction pour un type de données. Pour les usages, l'objectif est de retenir un ensemble de services, à la fois nécessaire et suffisant : tous les services utiles, rien que les services utiles. Pour la construction, l'objectif est de retenir tous les modes susceptibles de mener à une implémentation raisonnable.

Méthode de définition

Pour définir un type de données, nous suivrons la méthode générale suivante :

  1. déclarer dans une interface les méthodes :
    • déclarer les accesseurs, correspondant à tous les modes pertinents de construction,
    • déclarer les fabriques, méthodes permettant de construire des objets du type de données suivant tous les modes pertinents de construction,
    • déclarer les services liés aux usages du type de données ;
  2. implémenter l'interface dans une classe d'implémentation, après avoir choisi un mode particulier de construction :
    • définir les attributs, qui déterminent la structure de l'état d'un objet, suivant le mode choisi,
    • définir les constructeurs, qui initialisent directement les attributs suivant le mode choisi,
    • définir les fabriques, en appelant les constructeurs directement si elles correspondent au mode choisi, ou indirectement sinon, via les fabriques et les accesseurs du mode choisi,
    • définir les accesseurs, en manipulant les attributs en lecture ou en écriture directement s'ils correspondent au mode choisi, ou indirectement sinon, via les accesseurs et les fabriques du mode choisi,
    • définir les services accédant aux attributs indirectement via les accesseurs et construisant des objets indirectement via les fabriques.

On obtient finalement le schéma suivant.

Sorry, your browser does not support SVG.

Figure 1: Couches d'une classe d'implémentation

Cette structuration particulière reposant sur différentes couches d'abstraction permet de rendre l'implémentation des services indépendante

  • du mode de construction de l'état, à condition que les accesseurs permettent d'accéder à l'état de l'objet, quel que soit le mode choisi, et
  • de la classe d'implémentation, à condition que les fabriques permettent de construire les objets nécessaires.

Dans ce cas, le code implémentant les services peut être réutilisé tel quel, dans une autre classe d'implémentation. Depuis Java 8, de tels services indépendant des classes d'implémentation peuvent être non seulement déclarés mais aussi implémentés dans l'interface : il suffit de précéder leur définition par le mot clé default.

Exemple des entiers naturels

Pour les entiers naturels, on peut retenir deux modes de construction :

  • d'une part à partir d'entiers relatifs de type int,
  • d'autre part, à partir d'une définition inductive, fondée sur la constante zéro et la fonction successeur.

L'interface Nat contient

  • des accesseurs correspondant aux deux modes de construction,
  • des fabriques correspondant aux deux modes de construction,
  • des services correspondant aux opérateurs algébriques d'un monoïde additif et d'un monoïde multiplicatif.

Pour implémenter l'interface Nat, on adopte un mode de construction qu'on réalise concrètement.

Un premier mode : les entiers naturels comme relatifs positifs

L'état est formé d'un int, nécessairement positif. On associe à cet état un constructeur prenant un argument de type int pour initialiser l'attribut.

La première fabrique correspond à ce constructeur : on l'implémente en appelant simplement le constructeur. Les deux autres fabriques correspondent au mode inductif de construction : la première permet de construire zéro, la seconde un successeur. Pour les implémenter, on réalise une conversion du mode inductif vers les entiers relatifs, puis on appelle la première fabrique.

Le premier accesseur correspond au mode choisi ; c'est un simple "getter" permettant d'obtenir la valeur de l'unique attribut de type int. Les deux autres accesseurs correspondent au mode inductif : ils permettent de sélectionner le cas inductif (cas de base avec zéro ou cas construit avec le successeur) et de décomposer le cas construit (pour obtenir le prédécesseur). Pour les implémenter, on utilise le premier accesseur et la première fabrique au besoin, tous deux correspondant au mode choisi d'une représentation par un int.

Les services algébriques, définissant les opérations algébriques et les éléments neutres associés, sont finalement implémentés en utilisant les fabriques et les accesseurs. Plusieurs implémentations sont possibles : on peut choisir le point de vue de l'efficacité, en réalisant les calculs avec des int.

Un second mode : les entiers naturels inductifs

On distingue deux cas, suivant la définition récursive suivante : un entier naturel est soit zéro, soit le successeur d'un entier naturel. Pour chaque cas, on développe une classe d'implémentation. L'état de zéro est vide ; l'état d'un successeur contient un entier naturel, le prédécesseur. A ces deux états, on associe deux constructeurs, un dans chaque classe, permettant d'initialiser l'état.

Deux fabriques se définissent directement à partir de ces deux constructeurs. Pour la troisième et dernière fabrique prenant un argument de type int, l'implémentation redirige vers une des deux premières fabriques, suivant la valeur de l'argument.

Les accesseurs correspondant au mode inductif sont implémentés en accédant directement à l'état de chaque classe. L'implémentation de l'accesseur correspondant à l'autre mode de construction calcule un int à partir de l'état, d'une manière récursive: le cas de base est traité dans la classe représentant zéro, alors que le cas construit est traité dans la classe représentant les successeurs.

Pour définir les opérations algébriques, on peut également recourir à l'approche récursive. Avec cette implémentation alternative de celle précédente, on utilise les fabriques et les accesseurs du mode inductif de construction. Il est important de remarquer que les différentes alternatives pour implémenter les services peuvent être utilisées dans toute classe d'implémentation, puisqu'elles reposent sur une couche d'abstraction fournie par chaque classe d'implémentation : cette couche formée des fabriques et des accesseurs fait en effet partie de l'interface.

Author: Hervé Grall
Version history: v1: 2016-10-06; v2: 2018-10-02[style].
Comments or questions: Send a mail.
The webpage content is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.