Version sans JavaScript, en lecture seule : les ateliers, les quiz et l’exécution du code n’y sont pas. Version complète · Catalogue · Se connecter
Support de cours · chapitre 13
Les limites de la POO et les autres paradigmes
Les faiblesses de la programmation objet, d'autres paradigmes, et quelques questions à se poser avant d'ajouter une abstraction. Chapitre à lire.
Les faiblesses de la POO
La rigidité des hiérarchies
Elle est traitée au chapitre 4. Une hiérarchie de classes fixe dès le début du projet des choix dont on ne mesure les conséquences que beaucoup plus tard. On recommande donc d'utiliser la composition par défaut, et de ne recourir à l'héritage que lorsque la substituabilité est réelle.
La verbosité
Avant Java 8, trier une liste demandait une classe anonyme :
Collections.sort(canards, new Comparator<Canard>() {
public int compare(Canard a, Canard b) {
return Integer.compare(a.getPoids(), b.getPoids());
}
});
Filtrer puis trier en demandait deux, plus une boucle :
List<Canard> legers = new ArrayList<Canard>();
for (Canard c : canards) {
if (c.estEnPlastique() && c.getPoids() <= 200) legers.add(c);
}
Collections.sort(legers, new Comparator<Canard>() {
public int compare(Canard a, Canard b) { return a.getCouleur().compareTo(b.getCouleur()); }
});
Cela fait beaucoup de code pour deux opérations simples. Java 8 a introduit les lambdas et les flots (streams), qui permettent d'écrire :
List<String> couleurs = canards.stream()
.filter(c -> c.estEnPlastique() && c.getPoids() <= 200)
.map(c -> c.getCouleur().toUpperCase())
.collect(Collectors.toList());
Le code est plus court et son intention se lit directement. C'est toujours du Java, avec une syntaxe plus légère.
La sur-ingénierie
Supposons qu'il faille seulement créer, lire, modifier et supprimer un canard. On trouve parfois, pour ce besoin, six interfaces (ServiceCanard, DepotCanard, UniteDeTravail, MappeurCanard, FabriqueDepot, FabriqueMappeur), quatre fabriques, des DTO, des mappeurs et des transactions.
public void creer(CanardDto dto) {
UniteDeTravail u = FabriqueUnite.creer();
DepotCanard depot = depots.creerDepotCanard(u);
MappeurCanard map = mappeurs.creer();
depot.sauver(map.depuisDto(dto));
u.valider();
}
Le même besoin, sans abstraction superflue :
public final class ServiceCanard {
private final DepotCanard depot;
public ServiceCanard(DepotCanard depot) { this.depot = depot; }
public void creer(Canard c) { depot.sauver(c); }
}
D'autres paradigmes
La programmation fonctionnelle
final class Canard {
private final String nom;
private final int poids;
Canard(String nom, int poids) { this.nom = nom; this.poids = poids; }
String nom() { return nom; }
int poids() { return poids; }
}
static int rationAliment(Canard c) {
int base = c.estEnPlastique() ? 0 : 50;
return base + Math.max(0, (c.poids() - 100) / 10);
}
Les données sont immuables et la fonction n'a ni état ni effet de bord : son test tient en deux lignes et ne demande aucun objet simulé.
Une organisation courante s'appelle cœur fonctionnel, coquille objet : les règles métier sont écrites sous forme de fonctions pures, et la POO est utilisée autour pour les entrées-sorties, l'orchestration et les états de longue durée.
La programmation orientée données
On décrit d'abord la forme des données, et le comportement ensuite. C'est l'approche habituelle des API et du stockage.
{ "id": "D42", "type": "plastique", "poids": 180 }
Les versions récentes de Java permettent d'écrire cela en une ligne avec un record. Le site compile en Java 8, où il faut encore une classe immuable complète : c'est la verbosité décrite plus haut, et les record ont été créés pour l'éviter.
Le modèle ECS et le modèle acteur
Le modèle ECS (entité, composant, système) pousse la composition à l'extrême : l'entité n'est qu'un identifiant, les composants sont des données sans logique, et les systèmes parcourent les entités qui possèdent un composant donné. On l'utilise surtout dans le jeu vidéo et la simulation.
Dans le modèle acteur (Akka), chaque acteur a un état isolé et ne communique que par messages. Aucun état n'étant partagé, les verrous deviennent inutiles. Ce modèle sert pour les systèmes concurrents et tolérants aux pannes.
Ces deux modèles ne sont pas au programme ; ils sont mentionnés pour que vous en connaissiez le nom.
Les règles de décision
La liste de contrôle
- Ai-je imposé des méthodes inutiles par héritage ?
- Puis-je remplacer cet héritage par un rôle (une interface) et de la composition ?
- Ce code est-il verbeux sans rien apporter ?
- Ce patron résout-il un problème réel, présent dans le code actuel ?
- L'abstraction que je viens de créer simplifie-t-elle le code, ou sert-elle seulement à me rassurer ?
- Le test unitaire est-il plus facile à écrire après ce choix qu'avant ?