UP | HOME

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() ...; // 
  }
Lors de la compilation de l'appel x.val(), en quelle déclaration le nom val se résout-il ?
  • D1
  • D2

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() ...; // 
  }
Lors de l'exécution de l'appel x.val(), à quelle méthode le nom val est-il lié ? Autrement dit, quelle est la méthode qui s'exécute ?
  • D1
  • D2

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() ...; // 
  }
Lors de la compilation de l'appel x.val(), en quelle déclaration le nom val se résout-il ?
  • D1
  • D2

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() ...; // 
  }
Lors de l'exécution de l'appel x.val(), à quelle méthode le nom val est-il lié ? Autrement dit, quelle est la méthode qui s'exécute ?
  • D1
  • D2

Last Updated 2015-09-30T18:43+0200. Comments or questions: Send a mail.