Factorisation par héritage multiple et agrégation avec délégation - Exemple des entiers naturels
Table of Contents
Session 2 - Travaux pratiques - A rendre pour le 10 novembre 2016, 8h00
Ce TP initie à la factorisation dans une architecture en couches. Il propose de développer les deux méthodes optimales permettant une double factorisation, de la couche haute et de celle basse :
- héritage multiple (en Java 8 uniquement),
- agrégation avec délégation.
On s'intéresse aux entiers naturels et aux deux représentations développées lors du TP1 :
- une représentation par des int positifs,
- une représentation par une suite de chiffres décimaux.
La possibilité d'une factorisation doit se comprendre dans le cadre de la discipline suivie, qui requiert de stratifier une classe d'implémentation en plusieurs couches avec les dépendances locales suivantes en simplifiant légèrement :
- les accesseurs et les constructeurs dépendant des attributs,
- les fabriques dépendant des constructeurs,
- les services dépendant des accesseurs et des fabriques.
Figure 1: Couches d'une classe d'implémentation
On s'aperçoit alors qu'il est possible de réaliser quatre combinaisons, suivant que la couche haute des services utilise une représentation ou l'autre, et que la couche basse de l'état utilise une représentation ou l'autre.
Calcul | |||
---|---|---|---|
Via des int | Décimal | ||
Etat | Int positif | int / int | int / décimal |
Nombre décimal |
décimal / int | décimal / décimal |
Le but du TP est de réaliser successivement suivant les deux méthodes les quatre combinaisons possibles. L'ensemble des développements est à mener dans le paquet session2.tp. On pourra s'inspirer du TP1 et du TD2.
Héritage multiple
Recopier l'interface Nat du TP1 dans le paquet session2.tp.heritageMultiple. Y ajouter une méthode estEgal, équivalente à la méthode equals, qui ne peut être implémentée dans une interface.
boolean estEgal(Object obj);
Dans le même paquet, développer les quatre combinaisons possibles en procédant ainsi.
- Couche haute - Définir deux interfaces concrètes implémentant les calculs (méthodes
somme, zero, produit, un, div, mod et estEgal) :
- AlgebreNatDecimal, qui utilise la représentation décimale pour calculer,
- AlgebreNatParInt, qui utilise la représentation par des int positifs pour calculer.
- Couche basse - Définir deux classes abstraites implémentant l'état, le
constructeur associé, les accesseurs, la méthode equals et la fabrique
correspondant à l'autre point de vue :
- NombreDecimal, qui utilise la représentation par une suite de chiffres décimaux de type String,
- IntPositif, qui utilise la représentation par des int positifs.
- Composition - Réaliser les quatre combinaisons possibles, par héritage d'une des classes abstraites et implémentation d'une des interfaces. A chaque fois, implémenter le constructeur et la fabrique non implémentée, celle correspondant au point de vue adopté dans la classe abstraite. Rendre le constructeur privé et définir une fabrique comme constante statique publique (à initialiser en appelant le constructeur).
Voici en résumé le diagramme des types à obtenir.
Agrégation avec délégation
Recopier l'interface Nat du TP1 dans le paquet session2.tp.agregation. Déplacer les accesseurs dans une nouvelle interface, EtatNaturel.
public interface Nat extends EtatNaturel, FabriqueNaturels<Nat>, SemiAnneauUnitaireEuclidien<Nat> { }
Définir une nouvelle interface correspondant à la couche basse. Elle contient les accesseurs et les fabriques.
public interface EtatNaturelPur extends EtatNaturel, FabriqueNaturels<EtatNaturelPur> { }
Dans le même paquet, développer les quatre combinaisons possibles en procédant ainsi.
- Couche basse - Définir deux classes implémentant l'interface
EtatNaturelPur (en ajoutant la méthode equals) :
- NombreDecimal, qui utilise la représentation par une suite de chiffres décimaux de type String,
- IntPositif, qui utilise la représentation par des int positifs.
- Couche haute - Délégation - Définir une classe abstraite NatDeleguantEtatNaturel
implémentant la délégation :
- un attribut de type EtatNaturelPur,
- un constructeur permettant d'initialiser l'attribut,
- une fabrique abstraite correspondant au constructeur (de visibilité protected) et à l'attribut,
- une méthode inverse de la fabrique, etat(), permettant de récupérer la valeur de l'attribut (de visibilité protected, utilisée seulement dans les tests),
- les accesseurs et les fabriques, utilisant l'attribut et la fabrique associée.
Couche haute - Implémentation - Définir deux classes d'implémentation de l'interface Nat par héritage de la classe abstraite NatDeleguantEtatNaturel :
- NatCalculantDecimal, qui utilise la représentation décimale pour calculer,
- NatCalculantInt, qui utilise la représentation par des int positifs pour calculer.
A chaque fois, implémenter la méthode equals et rendre le constructeur privé tout en définissant une fonction FAB permettant de construire une fabrique à partir d'un objet de type EtatNaturelPur.
Voici en résumé les diagramme de types à obtenir :
Tests
Les tests à passer sont fournis.
Paquet session2.tp.heritageMultiple
import session1.tp.FabriqueNaturels; public class Test { private static void test(FabriqueNaturels<Nat> fabN) { System.out.println("Test de la classe " + fabN.getClass()); Nat nA = fabN.creerNatAvecRepresentation("0000"); System.out.println("true ( 0 = 0) ? " + nA.equals(nA.zero())); nA = fabN.creerNatAvecRepresentation("001"); System.out.println("true ( 1 = 1) ? " + nA.equals(nA.un())); nA = fabN.creerNatAvecValeur(5); Nat nB = fabN.creerNatAvecValeur(3); System.out.println("8 (5 + 3) ? " + nA.somme(nB)); System.out.println("15 (5 * 3) ? " + nA.produit(nB)); } public static void main(String[] args) { test(NatParInt.FAB.creerNatAvecValeur(0)); test(NatParIntCalculantDecimal.FAB.creerNatAvecValeur(0)); test(NatDecimal.FAB.creerNatAvecRepresentation("")); test(NatDecimalCalculantInt.FAB.creerNatAvecRepresentation("000")); } }
Paquet session2.tp.agregation
import session1.tp.FabriqueNaturels; public class Test { private static void test(FabriqueNaturels<Nat> fabN) { System.out.println("Test de " + fabN.getClass()); System.out.println(" avec état de " + ((NatDeleguantEtatNaturel) fabN).etat().getClass()); Nat nA = fabN.creerNatAvecRepresentation("0000"); System.out.println("true ( 0 = 0) ? " + nA.equals(nA.zero())); nA = fabN.creerNatAvecRepresentation("001"); System.out.println("true ( 1 = 1) ? " + nA.equals(nA.un())); nA = fabN.creerNatAvecValeur(5); Nat nB = fabN.creerNatAvecValeur(3); System.out.println("8 (5 + 3) ? " + nA.somme(nB)); System.out.println("15 (5 * 3) ? " + nA.produit(nB)); } public static void main(String[] args) { test(NatCalculantDecimal.FAB(NombreDecimal.FAB.creerNatAvecRepresentation("000"))); test(NatCalculantInt.FAB(NombreDecimal.FAB.creerNatAvecRepresentation("000"))); test(NatCalculantDecimal.FAB(IntPositif.FAB.creerNatAvecValeur(0))); test(NatCalculantInt.FAB(IntPositif.FAB.creerNatAvecValeur(1))); } }