1. C'est quoi une interface ?
Si une classe abstraite, c'est un genre de "semi-produit" avec une implémentation partielle, alors une interface c'est juste une liste d'exigences : ce que doit savoir faire telle ou telle créature (classe) pour qu'on puisse l'utiliser dans un certain contexte abstrait.
En programmation, une interface c'est comme un "contrat" ou une "liste d'exigences" pour le comportement d'un objet. Elle décrit un ensemble de méthodes publiques, de propriétés, d'indexeurs et d'événements que la classe qui implémente cette interface doit fournir. L'interface dit : "Si tu veux t'appeler, par exemple, IDriveable (capable de rouler), alors sois sympa, fournis les méthodes Drive() et Stop()".
On peut imaginer une interface comme une Liste d'Exigences pour un Employé : par exemple, si tu veux embaucher des "Cuisiniers", dans l'interface il sera écrit : "Doit savoir cuisiner, servir des plats, se laver les mains". Comment chaque cuisinier va gérer ça — c'est son problème. L'essentiel, c'est qu'il respecte le contrat vu de l'extérieur.
Important : l'interface décrit seulement ce que la classe doit fournir, pas comment elle le fait.
En bref, en termes POO
- L'interface décrit l'"apparence" externe (API) d'un objet : quelles actions il permet de faire.
- Ne contient pas d'état (pas de champs). Dans la vision classique, les interfaces ne contiennent pas non plus d'implémentation, mais dans les versions modernes de C# il y a des exceptions (genre les méthodes par défaut), on en parlera plus tard.
- Une classe peut implémenter autant d'interfaces qu'elle veut (contrairement à l'héritage de classes !).
Une analogie dans la vraie vie
Le port USB — tout le monde sait qu'on peut brancher une souris, un clavier, une clé USB, un casque, même une cafetière (ouais, ça existe !). Ce qu'il y a dans la "souris" — on s'en fiche, tant qu'il y a une prise USB et que l'appareil supporte le bon protocole. Voilà, c'est ça une interface !
Pourquoi on a besoin des interfaces ?
- Ça réduit la dépendance. Le code bosse avec l'interface, pas avec une classe concrète. On programme "au niveau de l'interface".
- Permet d'avoir un héritage multiple du comportement : une classe peut implémenter plusieurs interfaces.
- Standardisation : on peut faire des mécanismes universels : par exemple, tous les objets qu'on peut comparer implémentent l'interface IComparable.
- Testabilité : facile de remplacer une implémentation concrète par un "faux" en test.
- Extensibilité : tu peux ajouter de nouveaux "modules" sans toucher au code existant.
2. Syntaxe de déclaration d'une interface
Allez, place au code ! En C#, une interface se déclare avec le mot-clé interface, et les noms d'interfaces commencent en général par I :
// Déclaration d'une interface
public interface IPrintable
{
// Méthode abstraite — du contrat
void Print();
// On peut aussi déclarer des propriétés
string Name { get; set; }
}
À retenir : les méthodes et propriétés d'une interface N'ONT PAS d'implémentation (pas de corps de méthode, juste la signature — comme pour les méthodes abstraites).
Points clés d'une interface
- On ne peut pas déclarer de champs (variables membres) dans une interface.
- Toutes les méthodes, propriétés, événements et indexeurs sont par défaut public (et doivent l'être dans l'implémentation).
- Une interface ne peut pas contenir de constructeurs (vu qu'elle n'a pas d'état).
- L'interface ne dépend pas du fait que la classe qui l'implémente soit abstraite ou concrète.
L'interface en action
Intégrons une interface dans notre appli d'apprentissage. Disons qu'on a maintenant une interface "imprimable" (IPrintable), que la classe Report implémente, et aussi une nouvelle classe Invoice.
public interface IPrintable
{
void Print();
string Name { get; set; }
}
Maintenant, on définit une classe qui implémente cette interface :
public class Report : IPrintable
{
public string Name { get; set; }
public Report(string name)
{
Name = name;
}
// Implémentation de la méthode de l'interface
public void Print()
{
Console.WriteLine($"Impression du rapport : {Name}");
}
}
Et maintenant — une classe complètement différente, mais avec la même interface :
public class Invoice : IPrintable
{
public string Name { get; set; }
public Invoice(string name)
{
Name = name;
}
public void Print()
{
Console.WriteLine($"Impression de la facture : {Name}");
}
}
Et maintenant, on peut écrire une méthode qui bosse avec n'importe quelle entité imprimable :
public static void PrintAnything(IPrintable printable)
{
printable.Print(); // Voilà ! Peu importe si c'est un rapport ou une facture — l'essentiel c'est que ça sait imprimer.
}
Et voilà un exemple d'utilisation :
var report = new Report("Rapport mensuel");
var invoice = new Invoice("Facture #12345");
PrintAnything(report); // Impression du rapport : Rapport mensuel
PrintAnything(invoice); // Impression de la facture : Facture #12345
Voilà comment les interfaces permettent d'écrire du code universel, extensible et stylé.
3. Implémentation des interfaces
Brancher une interface à une classe
Une interface est implémentée par une classe avec les deux-points (ouais, comme pour l'héritage) :
public class Ticket : IPrintable
{
public string Name { get; set; }
public void Print()
{
Console.WriteLine($"Impression du billet : {Name}");
}
}
Important : la classe doit implémenter tous les membres de l'interface. Et les membres implémentés doivent être public.
Si tu oublies d'implémenter au moins un membre :
public class BrokenTicket : IPrintable
{
// Implémentation de Print() manquante
public string Name { get; set; }
}
// Erreur de compilation : 'BrokenTicket' n'implémente pas le membre d'interface 'IPrintable.Print()'
Plusieurs interfaces
Une classe peut implémenter plusieurs interfaces, séparées par une virgule :
public interface IStorable
{
void Store();
}
public class MultiPurposeDoc : IPrintable, IStorable
{
public string Name { get; set; }
public void Print()
{
Console.WriteLine("Impression du document");
}
public void Store()
{
Console.WriteLine("Sauvegarde du document");
}
}
4. Pourquoi on a besoin des interfaces ?
Peut-être que tu te dis : "Ok, la syntaxe c'est bon. Mais à quoi ça sert dans la vraie vie, à part me compliquer la vie sur ce cours ?" Je te rassure ! Les interfaces, c'est un des outils les plus utilisés dans le dev pro.
Séparation des responsabilités et faible couplage (Decoupling / Loose Coupling) :
- Imagine que tu développes un lecteur de musique. Il s'en fiche d'où vient la musique — d'un fichier local, d'internet ou d'un CD. Ce qui compte, c'est que la source de musique puisse fournir un flux audio.
- Tu peux définir une interface IAudioSource avec une méthode GetAudioStream().
- Du coup, tu auras des classes FileAudioSource, InternetAudioSource, CDAudioSource, qui implémentent cette interface.
- Ton lecteur va bosser avec IAudioSource, sans connaître le type concret. Si demain il y a une nouvelle source, genre BluetoothAudioSource, t'as pas besoin de changer le code du lecteur ! Tu crées juste une nouvelle classe qui implémente IAudioSource. Ça rend ton système beaucoup plus flexible et facile à étendre. C'est ça, le faible couplage — les composants dépendent des abstractions (interfaces), pas des implémentations concrètes.
Polymorphisme et traitement uniforme :
Comme on l'a vu avec PrintAnything, tu peux avoir un ensemble d'objets de types différents, mais qui partagent un comportement commun décrit par l'interface. Tu peux appeler la même méthode (Print()) sur tous ces objets, sans savoir qui ils sont — rapport, facture ou billet. Ça permet d'écrire du code super concis et universel.
Tests unitaires (Unit Testing) :
C'est sûrement un des usages les plus importants des interfaces. Quand tu testes un composant de ton système, il a souvent besoin d'autres composants pour fonctionner (genre une classe qui sauvegarde des données dépend d'une classe qui bosse avec la base de données).
Au lieu de passer la vraie classe DatabaseSaver (qui a besoin d'une vraie base pour les tests !), tu peux passer un objet "faux" (ou "mocké") qui implémente juste l'interface IDataSaver. Cet objet "mocké" va juste simuler le comportement de sauvegarde, sans toucher à la vraie base. Ça permet de tester les composants isolément, vite et sans dépendances externes.
Développement d'API et de frameworks :
Quand tu crées une bibliothèque ou un framework, tu veux donner aux devs des "points d'extension". Les interfaces sont parfaites pour ça. Tu peux dire : "Si tu veux que ton composant marche avec mon système, implémente cette interface". Les libs standard .NET sont pleines d'interfaces (genre IEnumerable<T>, IDisposable, IComparable<T>) — elles définissent des contrats pour les scénarios les plus courants.
Programmer direct au niveau de l'interface (Programming to an Interface) :
Les devs expérimentés disent souvent : "Programme au niveau de l'interface, pas de l'implémentation". Ça veut dire que quand tu déclares le type d'une variable ou d'un paramètre de méthode, au lieu d'une classe concrète (Car), c'est mieux d'utiliser une interface (IDriveable). Ça rend ton code plus flexible et moins dépendant des détails d'implémentation, tu peux changer une implémentation par une autre facilement.
5. Erreurs typiques avec les interfaces
Erreur n°1 : essayer de créer une instance d'interface.
Tu peux écrire Cat murzik = new Cat("Murzik", 3);, parce que Cat c'est une classe concrète. Mais tu ne peux pas écrire ITalkable talker = new ITalkable();. L'interface, c'est juste un contrat, un modèle. Elle n'a pas d'implémentation et ne peut pas être instanciée direct. C'est comme un plan, pas une maison finie.
Erreur n°2 : oublier d'implémenter tous les membres de l'interface.
Si tu dis que ta classe implémente une interface, genre IMyInterface, alors elle doit implémenter toutes ses méthodes. Même une seule méthode oubliée va donner une erreur de compilation : MyClass n'implémente pas IMyInterface.TheMissingMethod().
Erreur n°3 : mauvais modificateurs d'accès à l'implémentation.
Les méthodes d'interface sont implicitement public, et dans l'implémentation elles doivent aussi être public. Si tu essaies de mettre la méthode en private ou protected, le compilateur va râler. T'as promis — tu dois l'implémenter ouvertement.
Erreur n°4 : essayer d'ajouter des champs ou des constructeurs dans une interface.
Les interfaces décrivent le comportement, pas l'état. Donc tu peux pas ajouter de champs ou de constructeurs dedans. Si tu tentes — erreur de compilation. Seules les propriétés sont autorisées, et encore — juste pour décrire les getters/setters.
Erreur n°5 : confusion entre override et l'implémentation d'interface.
Le mot-clé override sert à redéfinir les méthodes de la classe de base. Mais pour implémenter une interface, pas besoin — tu écris juste une méthode public avec la bonne signature. C'est un détail important qu'on oublie facilement.
GO TO FULL VERSION