Les points à retenir avant de coder
- Un type abstrait combine du code réutilisable et des points d’extension obligatoires.
- Il sert surtout quand plusieurs classes partagent le même squelette métier.
- Il n’est pas fait pour remplacer une interface quand il n’y a pas d’implémentation commune.
- Dans les langages à héritage simple de classe, son choix structure fortement l’architecture.
- Le bon critère reste la lisibilité du modèle, pas la volonté de “faire objet”.
À quoi sert un type abstrait
Je l’utilise quand je veux centraliser une partie du comportement tout en laissant certaines étapes à la charge des classes dérivées. La base porte alors les champs communs, des méthodes utilitaires et parfois le déroulé général d’un traitement, tandis que les variantes remplissent les points qui changent réellement.
Le cas typique, c’est un processus en plusieurs étapes: la base orchestre, les enfants complètent. C’est exactement ce que l’on retrouve avec le patron Template Method, où l’on protège la logique commune pour éviter que chaque implémentation la réécrive différemment.
En pratique, cette approche vaut surtout quand la similarité est forte sur la structure, mais pas totale sur le détail. Dès que je vois que plusieurs classes réécrivent le même enchaînement avec seulement deux ou trois blocs variables, je préfère factoriser. C’est ce qui donne une base plus stable pour la suite.
Reste maintenant à voir ce que cette base autorise concrètement et pourquoi elle ne doit pas être confondue avec une simple classe ordinaire.

Comment l’héritage se construit autour d’une base abstraite
La règle est simple: le type est pensé pour être étendu, pas instancié. Autrement dit, on peut définir des méthodes déjà prêtes, des membres protégés, parfois un constructeur, mais on laisse volontairement au descendant la responsabilité de compléter ce qui manque.
- La création directe est interdite parce que le type n’est pas complet.
- Les méthodes abstraites décrivent ce que la sous-classe doit fournir.
- Les méthodes concrètes partagent du code sans le dupliquer.
- L’état partagé peut exister, mais il doit rester utile et lisible.
- Le constructeur sert surtout à initialiser la base commune.
Dans Java, C# et PHP, cette logique pèse aussi sur l’architecture, car la hiérarchie de classe reste limitée à une seule branche principale. C’est précisément pour cela qu’il faut garder la base courte, lisible et orientée vers un seul rôle métier.
Quand cette structure est claire, la vraie question devient: faut-il vraiment un type abstrait, ou une interface ferait-elle mieux l’affaire ?
Quand choisir une classe abstraite plutôt qu’une interface
Je prends un type abstrait quand j’ai à la fois un contrat et un minimum de code commun. Si je n’ai qu’un nom de méthode à imposer, je préfère généralement une interface; si j’ai une trame partagée, un peu d’état et plusieurs étapes déjà définies, la base abstraite devient plus cohérente.
| Critère | Type abstrait | Interface |
|---|---|---|
| Code partagé | Oui, c’est souvent sa raison d’être | Peu ou pas, selon le langage |
| État commun | Oui, si cela a du sens métier | En général non |
| Objectif principal | Réutiliser une base d’implémentation | Définir une capacité ou un contrat |
| Souplesse d’extension | Bonne, mais plus contraignante | Meilleure quand plusieurs familles doivent coexister |
| Lisibilité du modèle | Excellente si la famille d’objets est homogène | Excellente si le comportement doit rester découplé |
Mon repère pratique est le suivant: si je peux factoriser sans forcer le design, la base abstraite se justifie. Si je dois tordre la hiérarchie pour la faire entrer dedans, je suis déjà en train de choisir le mauvais outil.
Et c’est justement là que les erreurs commencent à apparaître, souvent plus vite qu’on ne le croit.
Les erreurs que je vois le plus souvent
La première erreur consiste à créer une base sans vraie logique commune. On obtient alors une structure lourde qui n’apporte ni réutilisation nette, ni contrat lisible, ni vraie simplification.
La deuxième erreur, plus subtile, consiste à mettre trop d’état mutable dans la base. Plus on partage de variables internes, plus on rend les sous-classes dépendantes d’un fonctionnement caché. À ce stade, le débogage devient plus coûteux que la duplication qu’on voulait éviter.
- Trop d’abstraction quand une simple classe utilitaire suffisait.
- Trop d’héritage quand une composition aurait gardé le code plus souple.
- Trop de responsabilités quand la base finit par orchestrer, valider, stocker et formater en même temps.
- Trop de visibilité protégée quand la sous-classe accède à presque tout et contourne l’intention initiale.
- Trop de profondeur quand il faut suivre trois ou quatre niveaux d’héritage pour comprendre un seul comportement.
Je me méfie aussi des bases qui ne servent qu’à “faire propre” dans un diagramme. Un bon modèle doit simplifier la lecture du code réel, pas seulement la documentation. Pour rendre cela plus concret, un exemple minimal parle souvent mieux qu’une règle abstraite.
Exemple concret de base commune pour un traitement métier
Prenons un flux de paiement dans un backend ou une API. La partie commune peut vérifier les prérequis, lancer le traitement et journaliser la fin, tandis que le mode de débit change selon le moyen de paiement. Dans ce cas, la base abstraite garde le scénario, et les spécialisations n’implémentent que la zone variable.
abstract class Paiement {
protected final String reference;
protected Paiement(String reference) {
this.reference = reference;
}
public final void traiter() {
verifier();
debiter();
confirmer();
}
protected void verifier() {
// contrôle commun
}
protected abstract void debiter();
protected void confirmer() {
// journalisation commune
}
}
class PaiementCarte extends Paiement {
public PaiementCarte(String reference) {
super(reference);
}
@Override
protected void debiter() {
// logique spécifique carte
}
}
Ce qui compte ici, ce n’est pas la syntaxe, mais la séparation nette entre ce qui ne change pas et ce qui varie. En C# ou en PHP, on retrouverait la même logique générale: une base non instanciable, quelques comportements partagés et des méthodes obligatoires à compléter selon le cas.
- En Java, l’héritage de classe reste unique, donc la base doit valoir le coup.
- En C#, les membres à redéfinir sont souvent marqués `abstract`, puis `override` côté enfant.
- En PHP, le principe reste identique: on étend une base incomplète et on complète les méthodes obligatoires.
Si je veux que le scénario reste stable, je verrouille le déroulé avec une méthode finale; si je veux laisser la main à la sous-classe, je n’ouvre que les points de variation. C’est cette discipline qui fait la différence entre un design utile et une hiérarchie fragile.
Ce que je retiens avant de l’utiliser dans un projet
Je garde ce type de base quand il aide vraiment à modéliser une famille d’objets, pas quand il sert seulement à économiser quelques lignes. Dès que la hiérarchie commence à grossir sans clarifier le domaine, je reviens à une solution plus simple.
Dans la pratique, je me pose trois questions: est-ce que je partage du code utile, est-ce que j’impose un comportement précis, et est-ce que la spécialisation reste lisible pour quelqu’un qui découvre le projet ? Si la réponse est non à deux de ces trois points, je m’oriente plutôt vers une interface, une composition ou même une petite classe concrète bien découpée.
Un bon type abstrait ne cherche pas à impressionner. Il sert à rendre l’architecture plus stable, plus lisible et plus honnête sur ce qui est commun et sur ce qui ne l’est pas.