Interfaces
Notions théoriques
Qu'est-ce qu'une interface ?
Une interface définit un contrat : elle liste des méthodes et propriétés que les classes doivent implémenter. Contrairement à une classe abstraite, une interface ne contient (généralement) que des déclarations, pas d'implémentation.
interface IDescriptible // convention : nom commence par I
{
string Decrire(); // déclaration — pas de corps
}
class Produit : IDescriptible
{
public string Nom { get; set; } = "";
public decimal Prix { get; set; }
public string Decrire() // obligatoire — le contrat l'impose
=> $"{Nom} ({Prix:F2} €)";
}
IDescriptible p = new Produit { Nom = "Clavier", Prix = 49.99m };
Console.WriteLine(p.Decrire());
Convention de nommage
Les interfaces commencent par I (majuscule) : IComparable, IDisposable, IFormattable. C'est une convention universelle en .NET.
Implémenter plusieurs interfaces
Contrairement à l'héritage de classe (une seule parente), une classe peut implémenter plusieurs interfaces :
interface ISauvegardable
{
string Serialiser();
}
interface IValidable
{
bool EstValide();
string[] ObtenirErreurs();
}
class Etudiant : ISauvegardable, IValidable
{
public string Nom { get; set; } = "";
public double Note { get; set; }
public string Serialiser() => $"{Nom},{Note}";
public bool EstValide()
=> !string.IsNullOrWhiteSpace(Nom) && Note >= 0 && Note <= 20;
public string[] ObtenirErreurs()
{
var erreurs = new List<string>();
if (string.IsNullOrWhiteSpace(Nom)) erreurs.Add("Le nom est requis.");
if (Note < 0 || Note > 20) erreurs.Add($"Note {Note} invalide (0-20).");
return [.. erreurs];
}
}
Interface + héritage
Une classe peut hériter d'une classe parente et implémenter des interfaces :
class Produit : Article, IDescriptible, ISauvegardable
{
// hérite de Article, doit implémenter IDescriptible et ISauvegardable
}
Interfaces qui héritent d'interfaces
interface ILisable { string Lire(); }
interface IModifiable : ILisable // exige aussi Lire() (hérité)
{
void Ecrire(string contenu);
void Effacer();
}
Méthodes par défaut (C# 8+)
Depuis C# 8, une interface peut avoir des méthodes avec implémentation par défaut :
interface IAffichable
{
string Decrire();
void Afficher() => Console.WriteLine(Decrire()); // méthode par défaut
}
Classe abstraite vs interface
| Classe abstraite | Interface | |
|---|---|---|
| Implémentation partielle | Oui | Non (sauf défaut C# 8+) |
| Champs | Oui | Non |
| Constructeur | Oui | Non |
| Héritage multiple | Non (une seule parente) | Oui (plusieurs interfaces) |
| Usage typique | Partager du code entre classes liées | Contrat comportemental pour classes non liées |
Exemple pratique
interface IExportable
{
string ExporterCsv();
string ExporterJson();
}
interface IValidable
{
bool EstValide();
}
class Commande : IExportable, IValidable
{
public int Id { get; }
public string Client { get; set; } = "";
public decimal Montant { get; set; }
public string Statut { get; set; } = "En attente";
private static int _prochainId = 1;
public Commande() { Id = _prochainId++; }
public bool EstValide()
=> !string.IsNullOrWhiteSpace(Client) && Montant > 0;
public string ExporterCsv()
=> $"{Id},{Client},{Montant:F2},{Statut}";
public string ExporterJson()
=> $"{{\"id\":{Id},\"client\":\"{Client}\",\"montant\":{Montant:F2},\"statut\":\"{Statut}\"}}";
}
var commandes = new List<Commande>
{
new Commande { Client = "Alice", Montant = 149.99m, Statut = "Payée" },
new Commande { Client = "", Montant = 50m }, // invalide
new Commande { Client = "Bob", Montant = 89.50m, Statut = "Expédiée" },
};
Console.WriteLine("=== Export CSV ===");
Console.WriteLine("id,client,montant,statut");
foreach (IExportable c in commandes)
Console.WriteLine(c.ExporterCsv());
Console.WriteLine("\n=== Validation ===");
foreach (Commande c in commandes)
Console.WriteLine($"Commande #{c.Id} ({c.Client}) : {(c.EstValide() ? "valide" : "INVALIDE")}");
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Vous allez créer un système de notification avec interfaces.
Étape 1 — Définir l'interface INotification
Déclarez dans une interface uniquement ce qui est strictement nécessaire. Préférez plusieurs petites interfaces cohérentes (Interface Segregation Principle — le "I" de SOLID).
Étape 2 — Implémenter plusieurs canaux
Les trois classes implémentent le même contrat mais avec des logiques complètement différentes. Le code client qui reçoit une INotification n'a pas besoin de savoir s'il s'agit d'un email ou d'un SMS. C'est le polymorphisme via les interfaces.
Étape 3 — Service utilisant l'interface
NotificateurMultiCanal stocke des INotification, pas des NotificationEmail. On peut lui ajouter un canal webhook ou Slack sans modifier son code. C'est le principe Dependency Inversion (le "D" de SOLID).