Cours BUT Info

Ch. 4 — Encapsulation, surcharge, polymorphisme

Encapsulation par délégation et ses quatre objectifs, surcharge et chaînage de constructeurs, ArrayList face au tableau — puis la mécanique du polymorphisme : type de la référence, classe réelle et late binding.

Encapsulation par délégation

L'interface publique d'une classe est la liste des attributs et méthodes accessibles depuis l'extérieur ; encapsuler, c'est masquer les détails d'implémentation en limitant ce qui y figure. Mais que faire quand une classe existante répond « presque » au besoin — il manque des attributs, des méthodes, ou un accès modifié ? On crée une classe qui contient l'autre :

  • l'attribut principal (la classe encapsulée) est private ;
  • des méthodes déléguées reproduisent, si besoin, le comportement de la classe encapsulée : même signature, et elles invoquent simplement l'objet encapsulé ;
  • de nouveaux membres complètent le tout.
public class Worker {
    private Person pers;
    private double wages;
    // constructeurs en délégation
    public Worker(String s, LocalDate d, double v) {
        this.pers = new Person(s, d);
        this.wages = v;
    }
    // accesseur délégué
    public String getName() { return this.pers.getName(); }
    // traitement délégué
    public boolean olderThan(Worker s) { return this.pers.olderThan(s.pers); }
    // accesseur spécifique
    public double getWages() { return this.wages; }
}

Le détail qui montre que la délégation est maîtrisée : aucun paramètre des méthodes de Worker n'est de type Person — la classe encapsulée n'apparaît jamais à l'extérieur, elle est un détail d'implémentation.

Quatre objectifs de la délégation

Le cours décline l'encapsulation en quatre intentions, visibles dans ce que la classe enveloppante expose :

  • Extension : enrichir les fonctionnalités par de nouveaux attributs ou méthodes — Worker ajoute un salaire à Person ;
  • Masquage : ne pas déléguer une méthode la rend indisponible depuis l'extérieur — on restreint les fonctionnalités ;
  • Simplification : faciliter l'usage en multipliant les méthodes d'accès, avec de nouvelles signatures pour la même fonctionnalité ;
  • Sécurisation : intercaler une procédure de contrôle avant d'invoquer la méthode déléguée, au lieu de l'exposer telle quelle.

Surcharge et chaînage

Surcharger (overload), c'est définir plusieurs méthodes de même nom dans une même classe, en variant le nombre, le type ou l'ordre des paramètres — jamais le type de retour : void processing(int) et boolean processing(int) ne peuvent pas coexister.

La surcharge mène vite à de la duplication — l'ennemi du développeur. Le chaînage l'évite : une définition en invoque une autre, qui porte les valeurs par défaut, et le traitement n'apparaît qu'une seule fois.

void processing() { this.processing(0, "defaultValue"); }
void processing(int i) { this.processing(i, "defaultValue"); }
void processing(int i, String s) { /* le traitement, une seule fois */ }

Les constructeurs se chaînent de la même façon avec this(...) — « un autre usage du this » — pour fournir plusieurs manières de construire les instances sans répéter les affectations. Une contrainte forte : l'appel à this(...) doit être la première instruction du constructeur, ce qui interdit tout prétraitement des paramètres avant l'appel : découper un nom complet en nom et prénom puis appeler this(name, forename) ne compile pas.

ArrayList : la structure qui grandit

Le tableau a deux qualités — accès direct au k-ième élément, recherche et tri efficaces — et un défaut : sa taille fixe, à préciser dès l'instanciation, alors que l'information n'est pas toujours disponible à l'avance. ArrayList<E> garde l'accès direct et gère la croissance elle-même, au prix d'une réallocation coûteuse en cas de dépassement de capacité et d'opérations courantes peu efficaces. Ses méthodes usuelles : add (et sa surcharge add(int index, E element) pour une insertion positionnée), get, set, remove, size, isEmpty, clear.

Toujours typer ses ArrayList

Sans type paramétré, la liste stocke des Object et accepte "alice" et 42 côte à côte — mais tout traitement itéré échoue : al.get(i) + 1 ne compile plus, car + n'est pas défini sur Object. La pseudo-solution instanceof + cast par type fonctionne, mais reste à fuir : c'est exactement le problème que les génériques (ch. 10) règlent.

Le polymorphisme par le typage

Tout objet Java est aussi un Object : un objet peut donc avoir plusieurs types. Et la référence n'est pas l'objet :

  • le type de la référence définit les envois de messages autorisés ;
  • la classe de l'objet définit le traitement exécuté.
Object o = new String("myString");
System.out.println(o);   // affiche la chaîne...
// o.length();          // ... mais ne compile pas : length() n'existe pas dans Object

C'est le late binding : le compilateur ne vérifie que la cohérence entre le type de la référence et le message invoqué ; le code appelé n'est déterminé qu'à l'exécution. Les upcasts (vers plus générique) sont implicites et sûrs ; les downcasts (vers plus spécifique) sont explicites et peuvent échouer à l'exécution.

Ce mécanisme éclaire deux points du chapitre. D'abord pourquoi une ArrayList non typée affiche correctement ses éléments : toString() est défini dans Object, donc invocable sur tout objet, et le code exécuté est celui du type réel. Ensuite pourquoi equals doit respecter la signature boolean equals(Object) : la version simplifiée equals(Student) n'est pas une redéfinition et casse Arrays.equals comme assertEquals des tests.

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (this.getClass() != obj.getClass()) return false;
    Student other = (Student) obj;
    return this.nip == other.nip
        && this.name.equals(other.name)
        && this.forename.equals(other.forename);
}

L'annotation @Override signale qu'on redéfinit un comportement par défaut hérité de Object — classe sans aucun attribut, qui fournit aussi getClass() et clone().

A retenir

  • encapsuler par délégation : étendre, masquer, simplifier ou sécuriser ;
  • surcharge à la signature près, jamais par le retour ; chaînage en première instruction ;
  • la référence autorise, l'objet exécute : c'est le late binding ;
  • equals(Object) est la seule signature qui s'intègre aux boîtes à outils et aux tests.


coursOO_04.pdf

R2.01 · Cours 4 — Encapsulation, surcharge, chaînage et polymorphisme · 35 diapositives

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

Chargement du document…

Dernière mise à jour le

Sur cette page