Bonjour ! Dans la leçon précédente, nous avons exploré les classes abstraites. Vous avez appris à créer des classes de base non-instanciables qui servent de modèle à une famille de classes partageant une identité forte (relation "est un") et du code commun.
Aujourd'hui, nous allons compléter ce tableau en mettant les classes abstraites et les interfaces côte à côte. L'objectif est de vous armer pour prendre l'une des décisions d'architecture les plus importantes en programmation orientée objet : quand utiliser l'une plutôt que l'autre ? Maîtriser ce choix est un pas de géant vers la conception de systèmes logiciels robustes et flexibles, une compétence clé pour un développeur backend.
1. Le cœur de la différence : "Être" contre "Pouvoir faire"
La distinction la plus fondamentale entre une classe abstraite et une interface repose sur la nature de la relation que vous souhaitez modéliser.
La classe abstraite : une relation d'identité ("est un")
Comme nous l'avons vu, on utilise une classe abstraite pour définir le socle commun d'une famille d'objets. Il s'agit de factoriser l'état (attributs) et le comportement (méthodes) qui sont intrinsèques à l'identité même de cette famille.
- Un
Carréest uneForme. - Une
Voitureest unVehicule.
Cette relation d'héritage est forte et justifie de partager une base commune. La vidéo suivante résume parfaitement cette idée.
JAVA (Intermédiaire) - 34 - Differences entre classes abstraites et Interfaces
La chaîne "LES TEACHERS DU NET" explique très bien cette idée de relation forte.
Regardez ce passage qui distingue le rôle des classes abstraites de celui des interfaces. Relation forte : L'analogie avec l'héritage dans la vie réelle illustre pourquoi l'héritage de classe (et donc les classes abstraites) est réservé aux liens de parenté étroits entre les concepts.
L'interface : une relation de capacité ("peut faire")
Une interface, quant à elle, ne définit pas ce qu'un objet est, mais ce qu'il peut faire. Elle définit un contrat de comportement ou une capacité qui peut être appliquée à des classes potentiellement très différentes.
- Un
Avionet unOiseaupeuvent tous les deuxvoler(). Ils implémentent l'interfaceVolant. - Un
Boutonet uneImagepeuvent êtrecliquables. Ils implémentent l'interfaceCliquable.
Notez qu'un Bouton n'est pas une Image, mais ils peuvent partager la même capacité. L'interface est l'outil parfait pour modéliser ces propriétés transversales.
JAVA (Intermédiaire) - 34 - Differences entre classes abstraites et Interfaces
La même vidéo illustre ce point avec un excellent exemple.
Regardez de l'interface Cliquable. Voyez comment cette capacité peut être appliquée à la fois à un Bouton et à une Image, deux classes qui n'ont aucun autre lien de parenté.
2. Comparaison technique
Au-delà du concept, des différences techniques importantes guident le choix. Voici un tableau récapitulatif :
| Caractéristique | Classe Abstraite | Interface |
|---|---|---|
| Héritage | Une classe ne peut hériter que d'une seule classe (abstraite ou non). | Une classe peut implémenter plusieurs interfaces. |
| Attributs | Peut avoir des attributs d'instance de tout type (private, protected...). |
Ne peut contenir que des constantes (public static final implicitement). |
| Constructeurs | Oui. Ils sont appelés par les constructeurs des classes filles via super(). |
Non. Une interface ne peut pas être instanciée. |
| Méthodes | Peut contenir un mélange de méthodes abstraites et concrètes. | Avant Java 8 : uniquement des méthodes abstraites. Depuis Java 8 : peut aussi avoir des méthodes default (concrètes) et static. |
| Visibilité | Les membres peuvent être public, protected, ou private. |
Tous les membres sont public par défaut. |

3. Scénarios de décision
Appliquons ces connaissances à des scénarios concrets de développement.
Scénario 1 : Vous voulez partager du code commun
Si vos classes partagent non seulement une signature de méthode mais aussi une implémentation commune et des attributs, la classe abstraite est le meilleur choix.
Imaginez les classes Voiture et Camion. Elles héritent toutes les deux d'une classe VehiculeAMoteur. Cette classe abstraite peut contenir un attribut niveauCarburant et une méthode concrète faireLePlein() qui fonctionne de la même manière pour les deux.
public abstract class VehiculeAMoteur {
protected int niveauCarburant;
public void faireLePlein(int quantite) {
this.niveauCarburant += quantite;
System.out.println("Le plein est fait. Niveau actuel : " + this.niveauCarburant);
}
public abstract void demarrer(); // Chaque véhicule démarre différemment
}
Une interface ne pourrait pas contenir l'attribut niveauCarburant ni l'implémentation de faireLePlein() (avant les méthodes default).
Scénario 2 : Vous définissez un contrat public
Si vous voulez spécifier un comportement qu'une classe doit avoir, sans vous soucier de son implémentation, utilisez une interface. C'est le cas typique lorsque vous concevez une API.
L'exemple du pilote d'impression est un classique de l'industrie.
Méthodes abstraites, classes abstraites et interfaces
L'article du site Koor.fr illustre parfaitement comment les interfaces sont utilisées pour créer des systèmes "pluggables".
Lisez la section Un cas concret. Concentrez-vous sur l'architecture : L'interface PrinterDriver définit le contrat : printHeader(), printBody(), etc. Le système d'exploitation ne connaît que l'interface PrinterDriver. Différents fabricants (PrinterManufacturer1, PrinterManufacturer2) fournissent leurs propres implémentations. C'est ainsi que des APIs comme JDBC (pour les bases de données) ou les collections Java (List, Map) fonctionnent.
Scénario 3 : Vous avez besoin de flexibilité et de "multi-héritage"
Puisqu'une classe ne peut hériter que d'une seule autre classe, les interfaces sont la seule solution lorsque vous voulez qu'une classe adopte plusieurs "types" ou comportements.
Un OiseauDeGuerre dans un jeu pourrait être :
extends Personnage(pour hériter des points de vie, de l'inventaire...)implements Volant(pour la méthodedecoller())implements Combattant(pour la méthodeattaquer())
public class OiseauDeGuerre extends Personnage implements Volant, Combattant {
// ... implémentation des méthodes de Volant et Combattant
}
Ceci serait impossible avec des classes abstraites seules.
4. Comment choisir : une règle simple
Posez-vous la question suivante :
Est-ce que mes classes partagent une relation "est un" fondamentale et du code commun ?
- Oui -> Envisagez une classe abstraite.
Est-ce que je veux définir une capacité ou un contrat qu'une classe "peut faire", indépendamment de ce qu'elle est ?
- Oui -> Utilisez une interface.
5. Exercice pratique : Conception d'un mini-jeu
Appliquons ces principes pour concevoir la structure des entités d'un jeu de rôle.
Contexte :
- Le jeu a des
Guerrierset desMagiciens. Ce sont tous desPersonnages. Ils ont un nom et des points de vie. - Les
Magicienspeuvent lancer des sorts. Des entités non-joueur, comme unCristalMagique, peuvent aussi être affectées par la magie. Tout ce qui peut être ciblé par un sort doit avoir une méthodesubirSort(int degats). - Les
Guerrierspeuvent effectuer une parade. Certains monstres, comme leGobelinAgile, le peuvent aussi. Tout ce qui peut parer doit avoir une méthodeparer().
Votre mission :
-
Classe abstraite
Personnage:- Créez une classe abstraite
Personnageavec les attributsprotected String nometprotected int pointsDeVie. - Ajoutez un constructeur pour initialiser ces attributs.
- Ajoutez une méthode concrète
afficherInfos()qui affiche le nom et les points de vie.
- Créez une classe abstraite
-
Interface
CiblableParMagie:- Créez une interface
CiblableParMagieavec une seule méthode :void subirSort(int degats).
- Créez une interface
-
Interface
PeutParer:- Créez une interface
PeutPareravec une seule méthode :void parer().
- Créez une interface
-
Classes concrètes :
Guerrier: hérite dePersonnageet implémentePeutParer.Magicien: hérite dePersonnageet implémenteCiblableParMagie.CristalMagique: n'hérite de rien, mais implémenteCiblableParMagie.
-
Implémentation :
- Fournissez des implémentations simples pour les méthodes d'interface (par exemple, avec un
System.out.println). PoursubirSort, déduisez les dégâts des points de vie du personnage.
- Fournissez des implémentations simples pour les méthodes d'interface (par exemple, avec un
-
Test dans
main:- Créez des instances de
Guerrier,MagicienetCristalMagique. - Appelez
parer()sur votre guerrier. - Appelez
subirSort()sur votre magicien et sur votre cristal magique.
- Créez des instances de
Cette structure vous forcera à utiliser une classe abstraite pour la relation d'identité (Guerrier est un Personnage) et des interfaces pour les capacités partagées par des entités hétérogènes.
Conclusion
Vous avez maintenant une compréhension approfondie de deux des outils de conception les plus puissants de la POO. Savoir quand utiliser une classe abstraite ou une interface est ce qui distingue un simple codeur d'un architecte logiciel.
Points clés à retenir :
- Classe abstraite : Pour une relation d'identité forte ("est un"), partage de code et d'état au sein d'une même famille.
- Interface : Pour définir une capacité ("peut faire") ou un contrat, applicable à des classes diverses et pour permettre un "multi-héritage" de types.
Dans le prochain module, nous aborderons la gestion des exceptions. Maintenant que vous savez structurer votre code de manière robuste, nous allons apprendre à gérer les situations où les choses ne se passent pas comme prévu, une compétence indispensable pour tout développeur backend professionnel.