Classes Abstraites vs Interfaces
Fait partie de la section Programmation Orientée Objet du Journey Java de Coddy — leçon 38 sur 87.
Maintenant que vous comprenez à la fois les classes abstraites et les interfaces, une question courante se pose : quand devriez-vous utiliser l'une plutôt que l'autre ? Le choix dépend de ce que vous essayez de modéliser.
Utilisez une classe abstraite lorsque des classes partagent une base commune avec un état ou un comportement partagé. Les classes abstraites peuvent avoir des variables d'instance, des constructeurs et un mélange de méthodes abstraites et concrètes :
public abstract class Animal {
protected String name; // état partagé
public Animal(String name) {
this.name = name;
}
public void sleep() { // comportement partagé
System.out.println(name + " is sleeping");
}
public abstract void makeSound(); // doit être implémenté
}Utilisez une interface lorsque vous souhaitez définir une capacité que des classes non liées peuvent partager. Les interfaces se concentrent sur ce qu'un objet peut faire, et non sur ce qu'il est :
public interface Flyable {
void fly();
}
// Des classes sans lien de parenté peuvent partager cette capacité
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }Voici une comparaison rapide :
| Fonctionnalité | Classe abstraite | Interface |
|---|---|---|
| Variables d'instance | Oui | Uniquement des constantes |
| Constructeurs | Oui | Non |
| Héritage multiple | Non (un seul extends) | Oui (plusieurs implements) |
| Modificateurs d'accès | Tous | Public uniquement (pour les méthodes abstraites) |
Une règle pratique : si vous vous retrouvez à créer une abstract class avec seulement des méthodes abstraites et aucun état, une interface est probablement le meilleur choix.
Défi
FacileConstruisons un système de véhicules qui démontre quand utiliser des classes abstraites par rapport aux interfaces. Vous allez modéliser des véhicules qui partagent un état et un comportement communs via une classe abstraite, tout en ajoutant des capacités optionnelles via des interfaces.
Vous organiserez votre code sur cinq fichiers :
Vehicle.java: Créez une classe abstraite qui sert de fondation à tous les véhicules. Chaque véhicule possède un champbrand(String) et un champyear(int). Incluez un constructeur pour initialiser les deux champs, des méthodes getter pour chacun, et une méthode abstraitestartEngine()qui retourne une String. Ajoutez également une méthode concrètegetInfo()qui retourne :[brand] ([year]). C'est un cas d'utilisation parfait pour une classe abstraite — les véhicules partagent un état (brand, year) et certains comportements (getInfo), mais chacun démarre son moteur différemment.Convertible.java: Définissez une interface pour les véhicules qui peuvent décapoter leur toit. Cette capacité n'est pas liée à ce qu'un véhicule est — c'est quelque chose que certains véhicules peuvent faire. Déclarez deux méthodes :openRoof()etcloseRoof(), retournant toutes deux une String.Car.java: Créez une classe qui étendVehicleet implémenteConvertible. Une Car possède un champ supplémentairenumDoors(int). Utilisezsuperpour initialiser les champs hérités. ImplémentezstartEngine()pour retourner :[brand] car engine started. ImplémentezopenRoof()pour retourner :[brand] roof openingetcloseRoof()pour retourner :[brand] roof closing.Motorcycle.java: Créez une classe qui étendVehiclemais n'implémente PASConvertible— les motos n'ont pas de toit ! Une Motorcycle possède un champhasSidecar(boolean). ImplémentezstartEngine()pour retourner :[brand] motorcycle engine roaring.Main.java: Rassemblez tout pour illustrer la différence entre les classes abstraites et les interfaces. Vous recevrez quatre entrées : une marque de voiture, une année de voiture, une marque de moto et une année de moto.Créez une Car (avec 4 portes) et une Motorcycle (sans side-car). Tout d'abord, démontrez le comportement partagé de la classe abstraite en affichant
getInfo()pour les deux véhicules — ils héritent tous deux de Vehicle. Ensuite, affichez le résultat destartEngine()pour chacun — remarquez comment chaque type de véhicule implémente cela différemment.Enfin, démontrez la capacité de l'interface : puisque seule la Car implémente
Convertible, appelez et affichezopenRoof()suivi decloseRoof()sur la voiture uniquement.
Vous recevrez quatre entrées : la marque de la voiture (String), l'année de la voiture (int), la marque de la moto (String) et l'année de la moto (int).
Votre sortie devrait afficher six lignes au total. Remarquez comment les deux véhicules partagent l'état de la classe abstraite et la méthode getInfo(), mais seule la Car possède la capacité décapotable — cela illustre quand utiliser chaque approche !
Aide-mémoire
Utilisez une classe abstraite lorsque les classes partagent une base commune avec un état ou un comportement partagé. Les classes abstraites peuvent avoir des variables d'instance, des constructeurs et un mélange de méthodes abstraites et concrètes :
public abstract class Animal {
protected String name; // état partagé
public Animal(String name) {
this.name = name;
}
public void sleep() { // comportement partagé
System.out.println(name + " is sleeping");
}
public abstract void makeSound(); // doit être implémentée
}Utilisez une interface lorsque vous souhaitez définir une capacité que des classes non liées peuvent partager. Les interfaces se concentrent sur ce qu'un objet peut faire, pas sur ce qu'il est :
public interface Flyable {
void fly();
}
// Des classes non liées peuvent partager cette capacité
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }| Caractéristique | Classe abstraite | Interface |
|---|---|---|
| Variables d'instance | Oui | Uniquement des constantes |
| Constructeurs | Oui | Non |
| Héritage multiple | Non (extends unique) | Oui (implements multiples) |
| Modificateurs d'accès | N'importe lequel | Public uniquement (pour les méthodes abstraites) |
Ligne directrice : Si vous créez une classe abstraite avec seulement des méthodes abstraites et aucun état, une interface est probablement le meilleur choix.
Essayez vous-même
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Lire les entrées
String carBrand = scanner.nextLine();
int carYear = scanner.nextInt();
scanner.nextLine(); // consommer le saut de ligne
String motorcycleBrand = scanner.nextLine();
int motorcycleYear = scanner.nextInt();
// TODO: Créer une Car avec 4 portes
// TODO: Créer une Motorcycle sans sidecar (false)
// TODO: Afficher getInfo() pour les deux véhicules (démontre le comportement partagé de la classe abstraite)
// TODO: Afficher startEngine() pour les deux véhicules (démontre les différentes implémentations)
// TODO: Afficher openRoof() et closeRoof() pour la voiture uniquement (démontre la capacité de l'interface)
}
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO
Fichiers externesIntroduction à la POOClasses vs ObjetsLe mot-clé thisMéthodesChamps (Attributs)Méthode constructeurSurcharge de constructeurRécapitulatif - Calculatrice simple4Héritage
Héritage de base (extends)Le mot-clé superRedéfinition de méthode (@Override)Chaînage de constructeursLa classe ObjectHéritage simple et multiniveauPourquoi pas d'héritage multiple de classesRécapitulatif - Hiérarchie des employés7Méthodes spéciales et classe Object
Méthode toString()equals() et hashCode()Méthode clone()compareTo() et ComparableInterface ComparatorRécapitulatif - Tri personnalisé2Modificateurs d'accès et Encapsulation
Aperçu des niveaux d'accèsMéthodes Getter et SetterMasquage d'informationsLe mot-clé finalRécapitulatif - Gestionnaire de compte bancaire5Polymorphisme
Bases de la surcharge de méthodesRedéfinition de méthodes (Run-Time)Upcasting et DowncastingL'opérateur instanceofClasses et méthodes abstraitesRécapitulatif - Calculateur de formes8Concepts avancés de la POO
Composition vs HéritageAgrégation vs CompositionClasses internes, imbriquées et anonymesEnums et méthodes d'EnumRecords (Java 16+)Classes scellées (Java 17+)11Patrons de conception, partie 1
Introduction aux patrons de conceptionPatron SingletonPatron FabriquePatron MonteurPatron ObservateurPatron Stratégie3Propriétés de classe et membres statiques
Variables d'instance vs variables statiquesMéthodes statiquesBlocs statiquesConstantes (static final)Récapitulatif - Compteur et utilitaire6Interfaces et Classes Abstraites
Introduction aux InterfacesImplémentation d'InterfacesImplémentation d'Interfaces MultiplesDefault et Static dans les InterfacesClasses Abstraites vs InterfacesInterfaces FonctionnellesRécapitulatif - Système de Paiement9La généricité
Introduction à la généricitéClasses génériquesMéthodes génériquesParamètres de type bornésWildcards (?, extends, super)Récapitulatif - Conteneur générique12Patrons de conception, partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositePatron Itérateur