Cours BUT Info

Ch. 5-6 — Héritage et mécanismes fins

extends, super et protected pour factoriser l'état et le comportement, type réel contre type effectif, lookup dynamique et masquage d'attributs — jusqu'aux classes abstraites.

Pourquoi hériter ?

La redondance reste « l'ennemi numéro un du développeur ». Trois classes de comptes bancaires — StandardAccount, LimitedAccount, BonusAccount — dupliquent le même solde, le même numéro, les mêmes credit/withdraw : écrire le même code dans plusieurs classes n'est pas maintenable. extends définit une classe par rapport à une autre, en ne spécifiant que les changements : la sous-classe récupère tous les attributs et comportements accessibles de la mère.

public class BonusAccount extends StandardAccount {
    private double rate;
    public BonusAccount(Person p, String number, double rate) {
        super(p, number);                    // réutilise l'initialisation du parent
        this.rate = rate;
    }
    public void interestPayment() { super.credit(this.getBalance() * this.rate); }
}

Le niveau d'accès décide de ce qui passe : balance étant private, seul getBalance() y donne accès depuis la sous-classe ; number public reste manipulable directement. Java impose l'héritage simple : une classe ne dérive que d'une seule autre.

protected et la table des visibilités

Le modificateur protected (symbole UML #) ouvre un membre aux sous-classes, y comprises celles d'un autre paquetage. Le tableau comparatif du cours se retient ainsi : private ne sort jamais de la classe ; default (paquetage) s'arrête aux classes du même paquet ; protected franchit les paquetages mais seulement par héritage ; public est universel.

Type réel, type effectif

  • le type réel dépend de la classe ayant servi à la création de l'instance : il est figé ;
  • le type effectif est celui de la référence : il détermine les méthodes invocables et peut varier sur tout le chemin d'héritage jusqu'à Object.
StandardAccount cs = new BonusAccount(new Person("A"), "a", 0.03);
// type réel : BonusAccount — type effectif : StandardAccount

Le transtypage ne modifie pas l'objet, seulement la façon de le voir. L'upcast (vers plus générique) est implicite et toujours valide ; le downcast doit être explicite et peut échouer à l'exécution ; un cast entre classes sœurs est invalide — (LimitedAccount) une référence vers un BonusAccount n'a aucun sens, même si elles partagent un parent. instanceof est vrai pour toute la branche : BonusAccount, StandardAccount et Object répondent tous true pour une instance de BonusAccount.

instanceof n'est pas une stratégie

instanceof étant vrai pour toute la branche héritée, brancher un traitement sur ce test est le signe qu'une redéfinition (polymorphisme) serait plus propre.

Factoriser, étendre, spécialiser

Trois usages de l'héritage structurent le cours : factoriser l'état ou le comportement (mutualiser les définitions), étendre (ajouter des méthodes pour enrichir le comportement), spécialiser (redéfinir — overriding — pour altérer le comportement). Dans une redéfinition, on peut augmenter la visibilité, jamais la réduire. Le mot-clé super sert aux deux niveaux :

  • super(...) invoque le constructeur parent — première instruction obligatoire, exactement comme this(...) ;
  • super.m() réutilise le traitement parent pour l'étendre : this.preprocessing(); super.first(); this.postprocessing(); — c'est le motif de LimitedAccount.withdraw, qui contrôle le seuil avant de déléguer au retrait standard.

À ne pas confondre : l'overload (même nom, signatures différentes dans une même classe) se décide à la compilation ; l'override se décide à l'exécution.

Masquage d'attributs et lookup

Redéclarer un attribut dans la sous-classe ne le remplace pas : les deux champs coexistent. Avec A b2 = new B() où B masque anAttribute (42 chez A, 404 chez B), b1.anAttribute vaut 404 mais b2.anAttribute vaut 42 — l'accès direct dépend du type effectif de la référence. Une méthode redéfinie, elle, exécute le code de B dans les deux cas : la méthode invoquée dépend du type réel, l'attribut accédé du type effectif.

Le lookup (mécanisme de recherche de méthodes) explique pourquoi. Quand la méthode est public, la recherche part de la classe réelle de l'objet invoquant puis remonte la hiérarchie jusqu'à trouver une définition — mécanisme dynamique et ascendant, jamais vers les classes dérivées. Quand la méthode est private, elle est résolue statiquement : c'est la définition visible là où le message est envoyé qui s'exécute, sans égard au type réel. Enfin super.m() relance la recherche à partir de la superclasse. Les traces d'exécution A→B→C du cours font pratiquer ces trois règles sur des hiérarchies où les méthodes se redéfinissent partiellement.

La classe abstraite

Modéliser un jeu d'échecs fait surgir le cas limite : couleur, nom et position sont communs à toutes les pièces, mais move() n'a pas de définition générale. Deux mauvaises solutions : ne définir move que dans les sous-classes — impossible d'écrire un traitement générique après un upcast vers ChessPiece —, ou déclarer des méthodes creuses dans la mère : aucune sémantique, aucune garantie de redéfinition, et quel sens aurait une instance de ChessPiece — une pièce qui n'est ni tour, ni fou, ni pion ?

La solution : abstract class. Une classe abstraite comporte attributs et méthodes comme n'importe laquelle, peut déclarer des méthodes abstract sans corps (incompatibles avec private), ne peut pas être instanciée — new est interdit, mais un constructeur reste possible, appelé par super(...) — et n'a pas d'attribut abstrait.

public abstract class BankAccount {
    protected double balance;
    public void credit(double amount) { this.balance += amount; }
    public abstract void withdraw(double amount);  // dépend de la nature du compte
}

Les classes dérivées doivent concrétiser toutes les méthodes abstraites, sous peine d'être abstraites elles-mêmes — dans l'exemple W-X-Y-Z du cours, Y reste abstraite par choix et Z, vide, est la seule classe de la hiérarchie instanciable. En UML, tout élément abstrait s'écrit en italique (ou précédé de «abstract» à la main).

A retenir

  • extends + super : ne spécifier que les changements, réutiliser l'initialisation et les traitements ;
  • protected : l'entre-deux pensé pour l'héritage ;
  • type réel → code exécuté (lookup dynamique si public, statique si private) ; type effectif → messages autorisés et attributs accédés ;
  • l'abstrait décrit ce qu'on sait faire, sans dire comment.


coursOO_05.pdf

R2.01 · Cours 5 — Héritage et polymorphisme · 26 diapositives

Consultation seule — téléchargement désactivé

Chargement du document…

coursOO_06.pdf

R2.01 · Cours 6 — Mécanismes de l'héritage · 26 diapositives

Consultation seule — téléchargement désactivé

Chargement du document…

Dernière mise à jour le

Sur cette page