UP | HOME

Les entiers relatifs - Approches descendantes et ascendantes pour l'héritage

Table of Contents

Session 2 - Travaux pratiques - 1er juin 2015 - 15h00-17h45 - 2h45

  • A préparer à l'avance (temps de travail : quarante-cinq minutes).

Ce TP initie à la factorisation par héritage. Partant d'un type abstrait de données représentant des entiers relatifs, nous réaliserons plusieurs implémentations, suivant trois modes de construction d'un entier relatif :

  • valeur du type primitif int,
  • entier naturel signé (c'est-à-dire possédant un signe),
  • différence entre entiers naturels.

Le premier mode correspond à un point de vue pratique, établissant le lien avec le langage de programmation utilisé. Si le second point de vue correspond à la représentation usuelle et naïve d'un entier relatif comme un entier naturel signé, le dernier point de vue correspond à celui utilisé en mathématiques pour construire les entiers relatifs à partir des entiers naturels : c'est un exemple du procédé général de symétrisation.

Suivant une approche descendante, nous factoriserons les calculs algébriques dans une classe abstraite, dont hériteront trois classes d'implémentation, une par mode. Suivant une approche ascendante, nous étendrons deux classes d'implémentation en redéfinissant les calculs algébriques : chacune utilisera pour les calculs une représentation conforme non pas au mode retenu pour la factorisation, correspondant à une représentation par un int, mais à son mode propre, donc respectivement fondée sur des entiers naturels signés, et sur des différences entre entiers naturels.

Une interface pour les entiers relatifs

Notre intérêt pour les entiers relatifs est de nature algébrique puisqu'un des objectifs des TP est de définir une bibliothèque de structures algébriques, la plus générale possible. L'ensemble des entiers relatifs \(\{ \ldots, -1, 0, 1, \ldots \}\) peut être considéré comme un anneau unitaire, une fois muni de l'addition et de la multiplication.

Nous allons représenter le type de données correspondant aux entiers relatifs par une interface qu'on appellera Z. Comment définir l'interface Z ?

  • Comme le type vérifie des propriétés algébriques, l'interface Z héritera d'interfaces génériques spécifiant ces propriétés. On utilisera les interfaces du paquet cm1.demo2.hierarchie fourni dans l'archive.
  • Le type doit aussi permettre d'accéder à la valeur d'un entier relatif, suivant les trois points de vue considérés, correspondant chacun à un mode de construction particulier :
    • une valeur de type int,
    • un signe et une valeur absolue,
    • une différence entre deux entiers naturels.

    Par exemple, l'entier relatif -5 peut être représenté par un int, -5, un signe négatif et une valeur absolue de 5, et enfin toute différence valant -5, comme (0 - 5) ou (7 - 12). Ainsi, l'interface Z déclarera les accesseurs correspondant à ces trois points de vue.

  • Un objet de type Z est immutable, en lecture seulement donc : il ne peut pas être modifié après construction. Cette propriété permet de l'utiliser comme une valeur d'un type numérique primitif (int ou double), comme vous pourrez l'expérimenter. C'est la raison pour laquelle les accesseurs ne permettent pas de modifier l'état d'un entier relatif : ils ne comportent que des "getters", et aucun "setter". L'immutabilité a aussi pour conséquence la nécessité de construire de nouveaux entiers relatifs lors de chaque calcul. Plutôt que des constructeurs, on préfère appeler des méthodes particulières, les fabriques : elles permettent de s'affranchir de la dépendance vis-à-vis des classes d'implémentation. L'ensemble de ces fabriques sera déclaré dans une interface dont héritera l'interface Z. Cette interface exprimant une propriété, la capacité à construire des entiers relatifs, elle sera générique.

Accesseurs

Les accesseurs permettent d'observer un entier relatif suivant les trois points de vue. On utilise l'interface Nat du premier TP.

  • int val()

    Méthode renvoyant l'entier relatif de type int représentant l'entier relatif cible.

  • boolean estPositif() et boolean estNegatif()

    Méthodes testant respectivement si l'entier relatif cible est positif ou négatif (au sens large).

  • Nat valAbsolue()

    Méthode renvoyant la valeur absolue de l'entier relatif cible.

  • Nat diminuende()

    Méthode renvoyant le diminuende (ce qui doit être diminué) de la différence (soustraction) utilisée pour représenter l'entier relatif cible.

  • Nat diminuteur()

    Méthode renvoyant le diminuteur (ce qui diminue) de la différence utilisée pour représenter l'entier relatif cible.

Services universels

Ces deux méthodes sont déclarées dans la classe mère Object.

  • String toString()

    Méthode permettant d'obtenir une représentation de l'entier relatif, en base dix.

  • boolean equals(Object o)

    Méthode testant l'égalité entre l'objet cible et l'objet o passé en argument ; renvoie true si o pointe vers un entier relatif n et si l'entier relatif cible et n sont égaux (au sens mathématique), renvoie false sinon.

Interfaces parentes

Toutes ces interfaces sont génériques car elles expriment des propriétés.

FabriqueNaturels<T>

Cette interface déclare les fabriques d'entiers naturels qu'on souhaite utiliser. Ces fabriques sont utiles car les entiers relatifs peuvent être construits à partir d'entiers naturels.

  • T creerNatAvecValeur(int val)

    Méthode renvoyant un entier naturel de valeur l'argument val si val est positif ou nul, lançant une exception java.lang.IllegalArgumentException sinon.

  • T creerNatZero()

    Méthode renvoyant un entier naturel de valeur nulle.

  • T creerNatSuccesseur(T predecesseur)

    Méthode renvoyant le successeur de l'argument predecesseur.

FabriqueRelatifs<T>

Cette interface déclare les fabriques qu'on souhaite utiliser.

  • T creerZAvecValeur(int val)

    Méthode permettant de créer un entier relatif à partir d'un int.

  • T creerZNatSigne(boolean signe, Nat abs)

    Méthode permettant de créer un entier relatif à partir d'un booléen représentant le signe (true pour positif, false pour négatif) et d'un entier naturel représentant la valeur absolue de l'entier relatif.

  • T creerZRelatif(Nat diminuende, Nat diminuteur)

    Méthode permettant de créer un entier relatif à partir d'un couple d'entiers naturels, représentant une différence (soit diminuende - diminuteur).

AnneauUnitaire<T>

Cette interface générique déclare les services correspondant aux constantes et aux opérations unaires ou binaires qu'on souhaite utiliser avec les entiers relatifs. Noter que chaque opération est représentée par une méthode qui possède un paramètre implicite, l'entier relatif cible référencé par this.

  • T somme(T n)

    Méthode renvoyant un nouvel entier relatif de valeur la somme de l'entier relatif cible et de l'argument n.

  • T zero()

    Méthode renvoyant l'élément neutre de l'addition, soit zéro.

  • T oppose()

    Méthode renvoyant un nouvel entier relatif de valeur l'opposé de l'entier relatif cible.

  • T produit(T n)

    Méthode renvoyant un nouvel entier relatif de valeur le produit de l'entier relatif cible et de l'argument n.

  • T un()

    Méthode renvoyant l'élément neutre de la multiplication, soit un.

Approche descendante

L'approche descendante permet de factoriser des services de haut niveau dans une classe abstraite. Une classe d'implémentation hérite de cette classe abstraite et implémente concrètement les méthodes de bas niveau, en particulier les accesseurs et les fabriques, tout en définissant l'état et les constructeurs.

Factorisation des services algébriques

La classe abstraite AnneauZParInt implémente partiellement l'interface Z. Précisément, elle implémente les méthodes de l'interface AnneauUnitaire<Z> correspondant aux services algébriques. L'implémentation des méthodes suit le premier point de vue associé au mode de construction par un int : ainsi elle recourt

  • à l'accesseur val et
  • à la fabrique creerZAvecValeur.

Une première implémentation ZParInt

Cette première implémentation adopte le premier point de vue, suivant lequel un entier relatif est représenté par un int. Elle hérite de la classe abstraite AnneauZParInt.

Etat

  • int val

    Attribut privé ayant pour valeur un entier relatif de type int.

Constructeur

  • NatParInt(int val)

    Constructeur public initialisant l'attribut à l'argument val.

Une seconde implémentation ZParNarSigne

Cette seconde implémentation adopte le second point de vue, suivant lequel un entier relatif est représenté par un entier naturel signé. Elle hérite de la classe abstraite AnneauZParInt.

Etat

  • boolean signe

    Attribut privé donnant le signe (true pour positif, false pour négatif).

  • Nat valeurAbsolue

    Attribut privé donnant la valeur absolue de l'entier relatif.

Constructeur

  • ZParNatSigne(boolean signe, Nat valeurAbsolue)

    Constructeur public initialisant le signe et la valeur absolue.

Une troisième implémentation ZParDifference

Cette troisième implémentation adopte le point de vue mathématique habituel, suivant lequel un entier relatif est représenté par un couple d'entiers naturels, interprété comme le diminuende et le diminuteur d'une différence. L'ensemble des entiers relatifs est alors appelé le symétrisé de l'ensemble des entiers naturels. La classe ZParDifference hérite elle-aussi de la classe abstraite AnneauZParInt.

Etat

  • Nat diminuende, Nat diminuteur

    Attributs privés représentant les deux termes d'une différence.

Constructeur

  • ZParDifference(Nat diminuende, Nat diminuteur)

    Constructeur public initialisant les deux termes de la différence.

Approche ascendante

L'approche ascendante permet de factoriser dans une classe abstraite les couches de bas niveau formées de l'état, des accesseurs et des fabriques, possiblement d'autres méthodes. Une classe d'implémentation hérite de cette classe et la complète en implémentant les couches de haut niveau, formées des services utilisant les méthodes héritées. Il existe aussi une variante de cette approche, correspondant à une spécialisation ou à un raffinement. La classe d'implémentation hérite non plus d'une classe abstraite mais d'une classe concrète : dans ce cas, elle redéfinit certaines méthodes déjà définies dans la classe parente, méthodes qui sont dites alors spécialisées ou raffinées.

Une classe d'implémentation raffinant ZParNatSigne

La classe ZParNatSignePur hérite de la classe ZParNatSigne et redéfinit les services algébriques formés des méthodes déclarées dans l'interface AnneauUnitaire. Les calculs sont réalisés en prenant le point de vue correspondant au mode de construction, qui définit un entier relatif par un entier naturel signé. Ainsi l'implémentation des méthodes recourt :

  • aux accesseurs estPositif, estNegatif et valAbsolue, et
  • à la fabrique creerZNatSigne.

Une classe d'implémentation raffinant ZParDifference

La classe ZParDifferencePure hérite de la classe ZParDifference et comme la classe précédente, redéfinit les services algébriques formés des méthodes déclarées dans l'interface AnneauUnitaire. Les calculs sont réalisés en prenant le point de vue correspondant au mode de construction, qui définit un entier relatif par une différence entre deux entiers naturels. Ainsi l'implémentation des méthodes recourt :

  • aux accesseurs diminuende et diminuteur, et
  • à la fabrique creerZRelatif.

Travail à faire

  • Mettre à jour le projet des corrections en important l'archive déposée sur Campus.
  • Dans le projet dédié au développement :
    • Recopier si ce n'est déjà fait le paquet cm1.demo2.hierarchie du projet dédié aux corrections : ce paquet contient les interfaces décrivant les structures algébriques.
    • Renommer le paquet tp1 en tp1Perso pour éviter les conflits. Ce paquet contient vos propres développements pour le TP1.
    • Recopier du projet des corrections le paquet tp1 contenant la correction du TP1. On utilisera ce paquet dans le TP2.
    • Recopier enfin le paquet tp2 qui contient deux interfaces, une classe de test et une classe regroupant des fonctions utiles pour les entiers naturels.

Les développements suivants seront réalisés dans le paquet tp2. Voici en résumé le diagramme des types à obtenir.

Etude de l'interface Z (fournie)

Commenter l'interface Z. Celle-ci utilise les structures algébriques du paquet cm1.demo2.hierarchie ainsi que les interfaces utiles du paquet tp1 (contenant la correction du TP1).

Etude des tests (fournis)

Commenter les tests définis dans la classe Test.

Factorisation des services algébriques dans la classe abstraite AnneauZParInt

Définir la classe abstraite AnneauZParInt. Implémenter les cinq méthodes de l'interface AnneauUnitaire (somme, zero, oppose, produit et un) en utilisant les méthodes suivantes :

  • accesseur val,
  • fabrique creerZAvecValeur.

Implémentation ZParInt de l'interface Z

Définir la classe d'implémentation ZParInt par héritage de la classe abstraite AnneauZParInt.

  • Définir l'état.
  • Définir le constructeur.
  • Définir les fabriques d'entiers relatifs en appelant le constructeur, directement ou indirectement via un appel à la fabrique creerZAvecValeur.
  • Définir les accesseurs en accédant à l'état, directement ou indirectement via un appel à la méthode val.
  • Définir les fabriques d'entiers naturels en utilisant un tp1.NatParInt.
  • Définir les méthodes toString et equals en utilisant la méthode val.

Tester l'implémentation en décommentant les instructions pertinentes de la fonction principale de la classe Test.

Implémentation ZParDifference de l'interface Z

Définir la classe d'implémentation ZParDifference par héritage de la classe abstraite AnneauZParInt.

  • Définir l'état.
  • Définir le constructeur.
  • Définir les fabriques d'entiers relatifs en appelant le constructeur, directement ou indirectement via un appel à la fabrique creerZRelatif.
  • Définir les accesseurs en accédant à l'état, directement ou indirectement via un appel aux méthodes diminuende et diminuteur. Pour les calculs, on pourra utiliser les fonctions de la classe ModuleNat.
  • Définir les fabriques d'entiers naturels en utilisant un des Nat de l'état.
  • Définir les méthodes toString et equals en utilisant les accesseurs. Pour le test d'égalité, se ramener à des calculs sur les Nat suivant l'équivalence suivante, permettant de passer de différences à des sommes : \((d - t) = (d' - t')\) si et seulement si \((d + t') = (d' + t)\).

Tester l'implémentation en décommentant les instructions pertinentes de la fonction principale de la classe Test.

Implémentation ZParDifferencePure raffinant ZParDifference

Définir la classe d'implémentation ZParDifferencePure par héritage de la la classe ZParDifference.

  • Définir un constructeur appelant le constructeur de la classe parente, via l'instruction super(…);.
  • Implémenter les cinq méthodes de l'interface AnneauUnitaire en utilisant les méthodes suivantes :
    • accesseurs diminuende et diminuteur,
    • fabrique creerZRelatif.
  • Implémenter la fabrique creerZRelatif par un appel au constructeur.
  • Vérifier dans la classe parente que les implémentations des deux autres fabriques d'entiers relatifs appellent bien la fabrique creerZRelatif. Sinon, modifier le code dans la classe parente.

Tester l'implémentation en décommentant les instructions pertinentes de la fonction principale de la classe Test.

Implémentation ZParNatSigne de l'interface Z

Définir la classe d'implémentation ZParNatSigne par héritage de la classe abstraite AnneauZParInt.

  • Définir l'état.
  • Définir le constructeur.
  • Définir les fabriques d'entiers relatifs en appelant le constructeur, directement ou indirectement via un appel à la fabrique creerZNatSigne. Pour les calculs, on pourra utiliser les fonctions de la classe ModuleNat.
  • Définir les accesseurs en accédant à l'état, directement ou indirectement via un appel aux méthodes estPositif, estNegatif et valAbsolue.
  • Définir les fabriques d'entiers naturels en utilisant le Nat de l'état.
  • Définir les méthodes toString et equals en utilisant les accesseurs estPositif, estNegatif et valAbsolue.

Tester l'implémentation en décommentant les instructions pertinentes de la fonction principale de la classe Test.

Implémentation ZParNatSignePur raffinant ZParNatSigne

Définir la classe d'implémentation ZParNatSignePur par héritage de la classe ZParNatSigne.

  • Définir un constructeur appelant le constructeur de la classe parente, via l'instruction super(…);.
  • Implémenter les cinq méthodes de l'interface AnneauUnitaire en utilisant les méthodes suivantes :
    • accesseurs estPositif, estNegatif et valAbsolue,
    • fabriques creerZNatSigne et creerZRelatif.
  • Implémenter la fabrique creerZNatSigne par un appel au constructeur.
  • Vérifier dans la classe parente que les implémentations des deux autres fabriques d'entiers relatifs appellent bien la fabrique creerZNatSigne. Sinon, modifier le code dans la classe parente.

Tester l'implémentation en décommentant les instructions pertinentes de la fonction principale de la classe Test.

Last Updated 2015-06-03T11:10+0200. Comments or questions: Send a mail.