1. Intro
Une classe abstraite — c’est une classe que tu peux pas instancier direct avec new. Elle sert de base pour créer d’autres classes plus concrètes. C’est un peu comme un modèle ou un "plan", qui définit la structure générale et le comportement pour les classes dérivées, mais laisse les détails de l’implémentation à ces dernières.
Petite analogie pour que ce soit plus clair : imagine que t’as le concept de "Transport". Dans la vraie vie, personne ne roule sur un "Transport" abstrait (sauf dans sa tête). Y’en a qui roulent en voiture, d’autres naviguent en bateau, d’autres volent en avion. La classe abstraite, c’est ton plan "Transport", qui dit que tout transport doit pouvoir se déplacer (Move), mais comment il le fait, ça doit être défini dans les classes enfants.
// Exemple de classe abstraite
public abstract class Transport
{
public string Model { get; set; }
// Méthode abstraite — pas d’implémentation
public abstract void Move();
}
Si tu tentes de créer un objet du type Transport, tu vas te prendre une erreur de compil ! Essaie, tu verras, le compilateur va râler genre "impossible de créer une instance d’une classe abstraite".
Quand et pourquoi utiliser des classes abstraites ?
- Tu veux regrouper des caractéristiques et comportements communs à différents objets similaires au même endroit.
- Tu veux poser un "contrat" : genre, "tous les héritiers doivent pouvoir se déplacer".
- Y’a une partie de la logique commune, mais certains trucs doivent être précisés dans les classes dérivées.
La classe abstraite, c’est un super outil pour designer l’archi d’un gros système, où t’as des "features" communes à tous, mais chaque héritier doit gérer sa propre spécificité.
2. Différence entre classe abstraite et classe normale
C’est souvent là que ça coince, alors on va tout mettre au clair !
Classe abstraite vs Classe normale
- Classe normale : tu peux la créer avec new, elle peut avoir toutes ses méthodes implémentées.
- Classe abstraite :
- tu peux pas l’instancier direct (abstract dans la déclaration)
- elle peut contenir des méthodes implémentées et des méthodes abstraites (sans corps)
- elle peut avoir des champs, des propriétés, des constructeurs et même des méthodes statiques
public abstract class Animal
{
public string Name { get; set; }
public void Sleep()
{
Console.WriteLine("L’animal dort !");
}
public abstract void MakeSound();
}
3. Méthodes et propriétés abstraites
Méthode abstraite
Une méthode abstraite — elle est déclarée dans une classe abstraite, mais elle a pas de corps (pas d’implémentation). La classe dérivée doit l’implémenter comme elle veut.
public abstract class Animal
{
public string Name { get; set; }
// Méthode abstraite
public abstract void MakeSound();
}
Propriété abstraite
Tu peux aussi déclarer une propriété abstraite, comme ça tous les héritiers sont obligés de la définir :
public abstract class Shape
{
// Propriété abstraite
public abstract double Area { get; }
}
4. Héritage des classes abstraites
Une classe abstraite peut avoir des méthodes abstraites et des méthodes déjà codées. La classe dérivée doit implémenter toutes les méthodes et propriétés abstraites, sinon elle devient abstraite elle-même.
On va continuer notre "appli animaux" commencée dans les confs précédentes :
public abstract class Animal
{
public string Name { get; set; }
public void Sleep()
{
Console.WriteLine($"{Name} dort profondément !");
}
public abstract void MakeSound();
}
Maintenant, on crée une classe normale, pas abstraite :
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name} dit : Ouaf !");
}
}
Essaie de pas implémenter MakeSound() — tu verras une erreur de compil.
Schéma : hiérarchie de classes avec classe abstraite
+---------------------+
| Animal (abstraite)|
+---------------------+
| Name: string |
| + Sleep() |
| + MakeSound()* |
+---------------------+
^
|
+------+-------+
| |
Dog Cat
* MakeSound() — méthode abstraite
5. Le polymorphisme en action : comment marche l’abstraction en vrai
On va utiliser tout ça dans notre mini-appli. Imagine qu’on a une liste d’animaux — genre des chiens et des chats.
List<Animal> animals = new List<Animal>
{
new Dog { Name = "Rex" },
new Cat { Name = "Felix" }
};
foreach (var animal in animals)
{
animal.MakeSound(); // Appel polymorphe : chacun fait son truc
animal.Sleep(); // Appelle la méthode du parent
}
La classe Cat se code pareil :
public class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name} dit : Miaou !");
}
}
À quoi ça sert dans les vrais projets
Les classes abstraites sont super utilisées dans l’archi des grosses applis. Par exemple, dans les éditeurs graphiques, t’as souvent une classe abstraite Shape avec des méthodes genre Draw() et des propriétés comme Area. Les formes concrètes (rectangle, cercle) implémentent ces méthodes avec leurs propres formules. Grâce à ça, tu peux créer plein de formes différentes sans tout recoder.
6. Particularités et limites des classes abstraites
Impossible de créer une instance
Le compilateur va t’interdire de faire ça :
Animal a = new Animal(); // Erreur !
Tu peux instancier que les classes dérivées (concrètes).
Les classes abstraites peuvent contenir :
- Déclarations de méthodes abstraites (pas d’implémentation)
- Méthodes implémentées (avec un corps)
- Propriétés et champs (abstraits ou implémentés)
- Constructeurs — ouais, une classe abstraite peut avoir des constructeurs, mais ils sont appelés que par les héritiers, pas direct par le code qui crée un objet.
Les classes abstraites supportent l’héritage
Tu peux faire toute une hiérarchie d’abstractions :
public abstract class Creature
{
public abstract void Live();
}
public abstract class Animal : Creature
{
public abstract override void Live();
public abstract void MakeSound();
}
Si dans la chaîne d’héritage une classe n’implémente pas toutes les méthodes abstraites, elle doit être déclarée abstraite aussi !
7. Classes abstraites et constructeurs
Une classe abstraite peut avoir des constructeurs, qui seront appelés depuis les constructeurs des classes dérivées.
Point important : les constructeurs des classes abstraites sont souvent déclarés protected, pour bien montrer qu’ils sont faits pour les héritiers, pas pour créer des instances direct.
public abstract class Animal
{
public string Name { get; set; }
// Constructeur déclaré protected
protected Animal(string name)
{
Name = name;
Console.WriteLine($"Animal créé avec le nom {name}");
}
public abstract void MakeSound();
}
public class Dog : Animal
{
public Dog(string name) : base(name)
{
// base(name) appelle le constructeur d’Animal
Console.WriteLine("Chien créé");
}
public override void MakeSound()
{
Console.WriteLine($"{Name} dit : Ouaf !");
}
}
Pourquoi utiliser des constructeurs protected ?
- Clarté d’intention : protected montre clairement que le constructeur est pour les héritiers
- Évite les erreurs : même si le compilateur interdit déjà l’instanciation directe, le constructeur protected rend ça encore plus clair dans l’API, et évite toute ambiguïté
- Bonne pratique : ça suit les principes d’encapsulation et d’API propre
Tu peux aussi utiliser des constructeurs public dans les classes abstraites, mais protected reflète mieux leur but.
8. Comparaison méthodes abstraites vs virtuelles
Les méthodes abstraites — c’est un genre de méthodes virtuelles (elles sont toujours virtual en fait, même si on écrit pas le mot), mais tu peux pas mettre d’implémentation dedans. Si tu veux que certaines méthodes puissent être overridées (mais pas obligatoirement), utilise virtual. Si c’est obligatoire — alors abstract.
public abstract class Worker
{
// Chacun doit l’implémenter
public abstract void Work();
// On peut override, mais c’est pas obligé
public virtual void Rest()
{
Console.WriteLine("Pause standard !");
}
}
| Méthode abstraite | Méthode virtuelle | |
|---|---|---|
| Implémentation dans la classe de base | Non, juste la signature (déclaration sans corps) | Oui, y’a une implémentation par défaut |
| Où on peut déclarer | Uniquement dans une classe abstraite | Dans n’importe quelle classe non-sealed, y compris abstraite |
| L’héritier doit override ? | Obligatoire pour le premier héritier non-abstrait | Pas obligé. Peut override ou utiliser la version de base |
| Appel direct possible ? | Non (pas de corps) | Oui (la version de base s’exécute) |
| But principal | Forcer les héritiers à fournir leur propre implémentation. Définir un "contrat" | Laisser la possibilité de changer le comportement si besoin |
9. Utiliser les classes abstraites dans ton projet
On continue à faire évoluer notre appli d’apprentissage. Cette fois, on veut une classe abstraite pour gérer la saisie utilisateur (genre des formulaires différents).
public abstract class InputForm
{
public string Title { get; set; }
// Méthode abstraite qui gère la saisie
public abstract void ShowAndHandleInput();
}
public class LoginForm : InputForm
{
public override void ShowAndHandleInput()
{
Console.WriteLine($"=== {Title} ===");
Console.Write("Entre le login : ");
string login = Console.ReadLine();
Console.Write("Entre le mot de passe : ");
string password = Console.ReadLine();
Console.WriteLine("Authentification terminée !");
}
}
public class RegistrationForm : InputForm
{
public override void ShowAndHandleInput()
{
Console.WriteLine($"=== {Title} ===");
Console.Write("Choisis un login : ");
string login = Console.ReadLine();
Console.Write("Choisis un mot de passe : ");
string password = Console.ReadLine();
Console.WriteLine("Inscription terminée !");
}
}
Utilisation :
InputForm[] forms = new InputForm[]
{
new LoginForm { Title = "Connexion" },
new RegistrationForm { Title = "Inscription" }
};
foreach (var form in forms)
{
form.ShowAndHandleInput();
}
Cette approche devient de plus en plus puissante à mesure que ton appli grossit. La classe abstraite, c’est une super base pour créer des systèmes évolutifs, où les principes DRY et SOLID (ne pas se répéter, coder modulaire et extensible) sont tes meilleurs potes.
10. Erreurs classiques avec les classes et méthodes abstraites
Erreur n°1 : t’as oublié d’implémenter une méthode abstraite.
Si la classe dérivée n’implémente pas toutes les méthodes et propriétés abstraites, le compilateur va gueuler. Ça arrive souvent, surtout avec du code d’autres gens ou dans de grosses hiérarchies. Vérifie bien que t’as rien zappé d’important.
Erreur n°2 : tu veux faire une méthode à la fois abstract et static.
Ça marche pas. Les méthodes abstraites sont faites pour être implémentées dans les instances, alors que les statiques n’ont pas besoin d’instance. Les deux s’opposent, le compilateur va refuser.
Erreur n°3 : tu "bâcles" l’implémentation des membres abstraits.
Si t’as pas envie d’implémenter toutes les méthodes abstraites, tu dois rendre la classe abstraite aussi. C’est pas vraiment une erreur, mais ça peut montrer que ton archi est un peu floue. Parfois, ça vaut le coup de mettre une partie de l’implémentation dans une classe abstraite intermédiaire, pour simplifier la vie des enfants.
GO TO FULL VERSION