C# : Les interfaces
Contrats, implémentation multiple, polymorphisme - comment les interfaces structurent le code en C#.
C# : Les interfaces
Dans l'article sur les objets, on a vu que l'héritage permet de réutiliser du code, mais qu'une classe ne peut hériter que d'une seule classe parent. Les interfaces résolvent cette limitation.
# Qu'est-ce qu'une interface ?
Une interface est un contrat. Elle définit un ensemble de méthodes et propriétés qu'une classe doit implémenter, sans dire comment.
interface IVehicule
{
string Marque { get; }
void Demarrer();
void Arreter();
}interface IVehicule{ string Marque { get; } void Demarrer(); void Arreter();}Pas de code dans les méthodes, pas de constructeur, pas de champs - juste des signatures. L'interface dit quoi, la classe dit comment.
Par convention en C#, les noms d'interfaces commencent toujours par un I majuscule : IVehicule, IDisposable, IEnumerable.
# Implémenter une interface
Une classe qui implémente une interface doit fournir le code de chaque membre déclaré :
class Voiture : IVehicule
{
public string Marque { get; }
public Voiture(string marque)
{
Marque = marque;
}
public void Demarrer()
{
Console.WriteLine($"{Marque} démarre");
}
public void Arreter()
{
Console.WriteLine($"{Marque} s'arrête");
}
}class Voiture : IVehicule{ public string Marque { get; } public Voiture(string marque) { Marque = marque; } public void Demarrer() { Console.WriteLine($"{Marque} démarre"); } public void Arreter() { Console.WriteLine($"{Marque} s'arrête"); }}Si tu oublies d'implémenter Arreter(), le compilateur refuse de compiler. Le contrat est strict.
# Implémentation multiple
Contrairement à l'héritage (limité à un seul parent), une classe peut implémenter autant d'interfaces qu'elle veut :
interface IVehicule
{
void Demarrer();
void Arreter();
}
interface IElectrique
{
int Autonomie { get; }
void Recharger();
}
interface IConnecte
{
void SynchroniserGPS();
}
class TeslaModelS : IVehicule, IElectrique, IConnecte
{
public int Autonomie { get; } = 600;
public void Demarrer() => Console.WriteLine("Tesla démarre silencieusement");
public void Arreter() => Console.WriteLine("Tesla s'arrête");
public void Recharger() => Console.WriteLine("Recharge en cours...");
public void SynchroniserGPS() => Console.WriteLine("GPS synchronisé");
}interface IVehicule{ void Demarrer(); void Arreter();} interface IElectrique{ int Autonomie { get; } void Recharger();} interface IConnecte{ void SynchroniserGPS();} class TeslaModelS : IVehicule, IElectrique, IConnecte{ public int Autonomie { get; } = 600; public void Demarrer() => Console.WriteLine("Tesla démarre silencieusement"); public void Arreter() => Console.WriteLine("Tesla s'arrête"); public void Recharger() => Console.WriteLine("Recharge en cours..."); public void SynchroniserGPS() => Console.WriteLine("GPS synchronisé");}TeslaModelS respecte trois contrats en même temps. C'est la force des interfaces - elles permettent de composer des comportements sans les contraintes de l'héritage.
# Polymorphisme avec les interfaces
Le vrai pouvoir des interfaces, c'est le polymorphisme : traiter des objets différents de manière uniforme via leur contrat commun.
void FaireLeTrajet(IVehicule vehicule)
{
vehicule.Demarrer();
Console.WriteLine("En route...");
vehicule.Arreter();
}
Voiture peugeot = new Voiture("Peugeot");
Velo monVelo = new Velo(); // implémente aussi IVehicule
FaireLeTrajet(peugeot); // fonctionne
FaireLeTrajet(monVelo); // fonctionne aussivoid FaireLeTrajet(IVehicule vehicule){ vehicule.Demarrer(); Console.WriteLine("En route..."); vehicule.Arreter();} Voiture peugeot = new Voiture("Peugeot");Velo monVelo = new Velo(); // implémente aussi IVehicule FaireLeTrajet(peugeot); // fonctionneFaireLeTrajet(monVelo); // fonctionne aussiLa méthode FaireLeTrajet ne sait pas si c'est une voiture ou un vélo - elle sait juste que l'objet a Demarrer() et Arreter(). C'est ça le polymorphisme : le même code fonctionne avec des types différents.
# Implémentation par défaut (C# 8+)
Depuis C# 8, les interfaces peuvent fournir une implémentation par défaut pour certaines méthodes. Les classes qui implémentent l'interface n'ont pas besoin de la redéfinir, mais elles peuvent le faire.
interface ILogger
{
void Log(string message);
// Implémentation par défaut
void LogError(string message)
{
Log($"[ERREUR] {message}");
}
}
class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
// LogError est déjà implémenté par l'interface
}interface ILogger{ void Log(string message); // Implémentation par défaut void LogError(string message) { Log($"[ERREUR] {message}"); }} class ConsoleLogger : ILogger{ public void Log(string message) { Console.WriteLine(message); } // LogError est déjà implémenté par l'interface}C'est utile pour ajouter des méthodes à une interface existante sans casser toutes les classes qui l'implémentent déjà.
# Interface vs classe abstraite
La question revient souvent : quand utiliser quoi ? (Voir l'article sur les classes abstraites pour les détails.)
| Interface | Classe abstraite | |
|---|---|---|
| Héritage multiple | Oui (autant qu'on veut) | Non (un seul parent) |
| Champs | Non | Oui |
| Constructeur | Non | Oui |
| Implémentation | Par défaut uniquement (C# 8+) | Partielle ou complète |
| Quand l'utiliser | Définir un contrat commun | Partager du code commun |
La règle simple : si tu veux juste définir "ce qu'un objet sait faire" -> interface. Si tu veux aussi fournir du code partagé entre les enfants -> classe abstraite.
# La suite
- Les structs - value types personnalisés, différence avec les classes
- Les records - immutabilité et comparaison par valeur
- Les enums - énumérations et constantes nommées