Agrégation et héritage - Résolution et liaison
Table of Contents
La grande question : Comment construire des interfaces ou des classes ?
- Par agrégation
- La relation "a un"
- Par héritage
- La relation "est un"
- Avantages de l'héritage : factorisation du code et polymorphisme
Le cas des interfaces
Agrégation : la brique de base
// Un entier naturel interface Nat { // - a pour valeur un int. int val(); // - peut être nul ou non boolean estNul(); // - a un prédécesseur (s'il est non nul) Nat predecesseur(); } // Une fabrique générique d'entiers naturels interface FabriqueNaturels<T> { // a une méthode fabriquant zéro. T creer(); }
Héritage : la puissance de la factorisation
- Possibilité d'un héritage multiple
- Préservation des méthodes des interfaces parentes
- Possibilité d'une extension par agrégation
// Un entier naturel est // - un élément d'un semi-anneau unitaire et // - une fabrique d'entiers naturels. public interface Nat extends SemiAnneauUnitaire<Nat>, FabriqueNaturels<Nat> { ... // Extension par agrégation } // Un semi-anneau unitaire est un semi-anneau, un monoide multiplicatif // et est biunifère. interface SemiAnneauUnitaire<T> extends SemiAnneau<T>, MonoideMultiplicatif<T>, BiUnifere<T> // Héritage multiple {}
Deux particularités de l'héritage
- Possibilité de spécialiser une méthode
// Cas typique d'une interface possédant des fabriques (méthodes de fabrication) interface Nat1 { Nat1 creer(); // Une fabrique ... } interface Nat2 extends Nat1 { Nat2 creer(); // Une fabrique spécialisant // (et remplaçant) Nat1 creer() ... }
- Covariance possible pour le type de retour
- Invariance pour le type des arguments (la contravariance aurait été possible mais les concepteurs du langage ont préféré permettre la surcharge)
- Possibilité d'une surcharge : méthodes ayant le même nom mais des
types différents de paramètres
// Fabrique générique avec des méthodes de même nom interface FabriqueNat<T> { T creer(); T creer(T pred); T creer(int val); }
Le cas des classes
Agrégation : la brique de base (bis)
class NatParInt implements Nat { // Attributs private int val; // Constructeurs public NatParInt(int val){ if(val < 0) throw new IllegalArgumentException("Pas de Nat à patir d'un int négatif."); this.val = val; } // Méthodes (accesseurs, fabriques et services) @Override public Nat zero() { return this.creerNatAvecValeur(0); } ... }
Héritage : la puissance de la factorisation (bis)
- Héritage simple uniquement
- Préservation des attributs et des méthodes des classes parentes
- Possibilité d'une extension par agrégation
// Implémentation des entiers naturels non nuls (des successeurs) class Succ implements Nat { private Nat predecesseur; public Succ(Nat predecesseur) { this.predecesseur = predecesseur; } @Override public int val() { return 1 + this.predecesseur().val(); } } // Variante mémorisant la valeur de l'entier naturel class SuccMemoire extends Succ implements Nat { // Extension de l'état private int val; // Constructeur public SuccMemoire(Nat predecesseur) { super(predecesseur); // Appel du constructeur parent this.val = this.init(); } // Extension des services private int init() { ... } }
Une particularité très importante : la spécialisation
- Redéfinition d'une méthode
class Succ implements Nat { ... @Override public int val() { return 1 + this.predecesseur().val(); } } class SuccMemoire extends Succ implements Nat { ... @Override public int val() { return this.val(); } }
- Effet : remplacement de la méthode spécialisée par la méthode
spécialisante
Pas d'effet lors d'une conversion
SuccMemoire b = new SuccMemoire(...); Succ a = b; a.val() // -> accès à val dans SuccMemoire
- Possibilité d'appeler la méthode spécialisée de la classe parente avec super
class SuccMemoire extends Succ implements Nat { ... // Extension des services private int init() { return super.val(); // Appel de la méthode spécialisée dans Succ } //
Deux particularités secondaires
- Masquage des attributs
class A { public int x; } class B extends A { public int x; }
Les deux attributs existent dans un objet de la classe B mais celui déclaré dans B masque celui déclaré dans A.
Comment accéder au x déclaré dans A ?
Par conversion.
B b = new B(); A a = b; a.x // -> accès au *x* déclaré dans *A*
- Possibilité d'une surcharge
Cf. les interfaces.
Le polymorphisme
La relation de sous-typage
- Relation de sous-typage = fermeture réflexive et transitive de la
réunion \(\bigcup\)
- relation d'implémentation (implements)
- relation d'héritage (extends)
- Règle de subsomption
\[\small \begin{array}{c} A \mathtt{\ sous-type\ de\ } B \quad \quad e \mathtt{\ de\ type\ } A \\ \hline e \mathtt{\ de\ type\ } B \\ \end{array} \]
FabriqueNaturels<Nat> fab = new Succ(...); // Classe Succ sous-type de interface Nat sous-type de interface FabriqueNaturels<Nat> Succ s = new SuccMemoire(...); // Classe SuccMemoire sous-type de classe Succ
La liaison tardive
- Un appel de méthode exp.f(args)
- A la compilation : résolution du nom f
- Hypothèse 1: l'expression cible exp a pour type C.
- Hypothèse 2: les arguments args ont pour type A (une liste de types).
- Conclusion : le nom f est résolu en une déclaration d'une méthode f(B b) (avec B sur-type de A) dans la table des méthodes associée au type C.
- A l'exécution : liaison du nom f
- Hypothèse 1 : l'expression cible exp s'évalue en c, une référence à un objet d'une classe D sous-type de C.
- Hypothèse 2 : les arguments args s'évaluent en a (une liste de valeurs).
- Conclusion : le nom f est lié dans la table des méthodes de D à
la méthode correspondant à la déclaration déterminée par la
résolution. La méthode est appelée avec les arguments a.
\(\Rightarrow\) Possible appel d'une méthode spécialisante
Exercice - Résolution et liaison en pratique
Résolution 1
class Succ ... { ... public int val() { ... } // D1 } class SuccMemoire extends Succ ... { ... public int val() { ... } // D2 } ... public static void main(String[] args){ SuccMemoire x = ...; ... x.val() ...; // }
Liaison 1
class Succ ... { ... public int val() { ... } // D1 } class SuccMemoire extends Succ ... { ... public int val() { ... } // D2 } ... public static void main(String[] args){ SuccMemoire x = new SuccMemoire(); ... x.val() ...; // }
Résolution 2
class Succ ... { ... public int val() { ... } // D1 } class SuccMemoire extends Succ ... { ... public int val() { ... } // D2 } ... public static void main(String[] args){ Succ x = new SuccMemoire(); ... x.val() ...; // }
Liaison 2
class Succ ... { ... public int val() { ... } // D1 } class SuccMemoire extends Succ ... { ... public int val() { ... } // D2 } ... public static void main(String[] args){ Succ x = new SuccMemoire(); ... x.val() ...; // }