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 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é
}Utilisez une interface lorsque vous souhaitez définir une capacité que des classes sans lien 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 non liées 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 | Constantes uniquement |
| Constructeurs | Oui | Non |
| Héritage multiple | Non (un seul extends) | Oui (plusieurs implements) |
| Modificateurs d’accès | N’importe lequel | Public uniquement (pour les méthodes abstraites) |
Une règle pratique : si vous vous retrouvez à créer une classe abstraite avec uniquement des méthodes abstraites et aucun état, une interface est probablement le meilleur choix.
Défi
FacileConstruisons un système de véhicules qui montre quand utiliser des classes abstraites plutôt que des interfaces. Vous modéliserez des véhicules qui partagent un état et un comportement communs grâce à une classe abstraite, tout en ajoutant des fonctionnalités facultatives grâce à des interfaces.
Vous organiserez votre code sur cinq fichiers :
Vehicle.java: créez une classe abstraite qui sert de base à tous les véhicules. Chaque véhicule possède un champbrand(String) et un champyear(int). Ajoutez un constructeur pour initialiser les deux champs, des méthodes d'accès pour chacun d'eux, ainsi qu'une méthode abstraitestartEngine()qui renvoie une String. Ajoutez également une méthode concrètegetInfo()qui renvoie :[brand] ([year]). C'est un cas d'utilisation idéal pour une classe abstraite : les véhicules partagent un état (brand, year) et une partie de leur comportement (getInfo), mais chacun démarre son moteur différemment.Convertible.java: définissez une interface pour les véhicules dont le toit peut être escamoté. Cette capacité n'est pas liée à ce qu'est un véhicule : c'est quelque chose que certains véhicules peuvent faire. Déclarez deux méthodes :openRoof()etcloseRoof(), qui renvoient 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 renvoyer :[brand] car engine started. ImplémentezopenRoof()pour renvoyer :[brand] roof openingetcloseRoof()pour renvoyer :[brand] roof closing.Motorcycle.java: créez une classe qui étendVehicle, mais n'implémente PASConvertible: les motos n'ont pas de toit ! Une Motorcycle possède un champhasSidecar(boolean). ImplémentezstartEngine()pour renvoyer :[brand] motorcycle engine roaring.Main.java: rassemblez tous les éléments pour mettre en évidence la différence entre les classes abstraites et les interfaces. Vous recevrez quatre entrées : la marque d'une voiture, l'année d'une voiture, la marque d'une moto et l'année d'une moto.Créez une Car (avec 4 portes) et une Motorcycle (sans side-car). Commencez par démontrer le comportement partagé de la classe abstraite en affichant
getInfo()pour les deux véhicules. Ils héritent tous deux de cette méthode de Vehicle. Affichez ensuite le résultat destartEngine()pour chacun : remarquez comment chaque type de véhicule l'implémente différemment.Enfin, démontrez la capacité fournie par l'interface : puisque seule la Car implémente
Convertible, appelez et affichezopenRoof(), puiscloseRoof(), uniquement sur la voiture.
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 doit afficher six lignes au total. Remarquez que les deux véhicules partagent l'état de la classe abstraite et la méthode getInfo(), mais que seule la Car possède la capacité de toit escamotable. Cela illustre quand utiliser chacune de ces approches !
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 la nouvelle ligne
String motorcycleBrand = scanner.nextLine();
int motorcycleYear = scanner.nextInt();
// TODO: Créer une Car avec 4 portes
// TODO: Créer une Motorcycle sans side-car (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 des implémentations différentes)
// 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érateurEntraînez-vous par vous-même : Compilateur Java en ligne