CodeGym /Cours /C# SELF /Contrats d'interface

Contrats d'interface

C# SELF
Niveau 23, Leçon 3
Disponible

1. Introduction

Alors, on a vu que les interfaces, c'est des contrats puissants. Mais c'est quoi programmer au niveau des interfaces ? C'est une philosophie de design qui dit : "Dépend des abstractions, pas des implémentations concrètes."

On reprend une analogie. Tu veux faire du café. Tu achètes une machine à café. Tu t'en fiches que ce soit le modèle concret "Super-Puper-Machine V3000" ou "Mega-Automate Barista 5000", non ? Ce qui compte, c'est que la machine sait "faire du café" – donc, elle réalise le "contrat" ICoffeeMaker. Tu appuies juste sur le bouton "Faire du café", et elle le fait. Comment elle le fait – avec du café moulu, des capsules ou autre – toi, en tant qu'utilisateur, tu t'en fiches.

Dans le code, ça veut dire qu'au lieu de demander dans tes méthodes un type concret comme Document ou Image, tu vas demander IPrintable. Ton code va marcher avec n'importe quel objet qui implémente cette interface, sans connaître son type précis.

Le contrat d'interface — c'est la garantie que chaque classe qui implémente l'interface va forcément fournir les définitions de tous les membres de l'interface (méthodes, propriétés, events). Pour le programme, tout objet qui implémente l'interface supporte un «ensemble de fonctionnalités» précis, et tu peux compter dessus sans savoir comment c'est fait à l'intérieur.

Imagine, t'as convenu avec un pote que vous vous retrouvez toujours près du canapé rouge dans le hall. Comment il y arrive — ascenseur, à pied ou en courant sur les murs — on s'en fout. L'important : il sera là. Ben l'interface, c'est le «canapé rouge» pour le code.

Exemple


public interface IPrintable
{
    void Print();
}

Contrat : n'importe qui qui implémente IPrintable doit savoir s'imprimer à l'écran. Comment — c'est les détails.

2. Le contrat comme point d'interaction entre les parties du système

Comment ça marche en pratique

Un système peut être composé de dizaines de classes, créées par des gens différents, à des moments différents, pour des buts différents. Mais si tous suivent le même contrat (c'est-à-dire implémentent la même interface), tu peux les utiliser de la même façon.

Exemple d'une vraie appli : imagine une base de clients. Classe Customer, classe Employee, classe Contractor. Chacun stocke ses infos à sa façon, mais si tous implémentent, par exemple, l'interface IContactInfo, qui demande les méthodes GetEmail() et GetPhoneNumber(), alors pour le code externe, on s'en fiche du type de l'objet — l'important, c'est qu'on peut choper l'email et le téléphone.


public interface IContactInfo
{
    string GetEmail();
    string GetPhoneNumber();
}

public class Customer : IContactInfo
{
    public string Email { get; set; }
    public string Phone { get; set; }

    public string GetEmail() => Email;
    public string GetPhoneNumber() => Phone;
}

// Pareil pour Employee et Contractor...

Maintenant, si tu veux imprimer les infos de tous ceux avec qui ta boîte a un contact (peu importe qui c'est), tu parcours juste la liste IContactInfo, tu appelles les méthodes qu'il faut — et ça roule.

3. Programmer "au niveau des interfaces"

Programmer au niveau des interfaces — ça veut dire écrire du code qui dépend pas des classes concrètes, mais seulement des interfaces (donc des contrats). Une classe qui implémente une interface peut être ce que tu veux, tant qu'elle respecte le contrat de l'interface.

Pourquoi c'est important ?

  • Scalabilité : tu peux ajouter facilement de nouveaux types sans toucher au code existant.
  • Testabilité : tu peux facilement remplacer des objets par des mocks (fakes) pour les tests.
  • Flexibilité : l'implémentation peut changer tous les jours, l'interface reste stable.
  • Propreté de l'archi : tes modules sont faiblement couplés, tu peux les réutiliser.

Exemple — Programme connu : "Compte bancaire"

Dans les exemples précédents, on avait une classe abstraite BankAccount avec une méthode abstraite Withdraw(), et les types concrets (SavingsAccount, CheckingAccount) faisaient les détails.

Maintenant, on va ajouter une interface — par exemple, pour afficher les infos du solde :


public interface IBalanceReporter
{
    void ReportBalance();
}

public abstract class BankAccount : IBalanceReporter
{
    public double Balance { get; set; }

    public abstract void Withdraw(double amount);

    public void ReportBalance()
    {
        Console.WriteLine($"Solde actuel : {Balance} euros");
    }
}

Maintenant, tous ceux qui bossent avec IBalanceReporter peuvent appeler ReportBalance() sans se soucier du type précis du compte.

4. Traitement générique et polymorphisme via le contrat

Exemple : handler générique

Dès qu'on bosse avec une interface, on peut faire des méthodes génériques qui se fichent du type de l'objet :


static void PrintAllBalances(IBalanceReporter[] accounts)
{
    foreach (var reporter in accounts)
    {
        reporter.ReportBalance();
    }
}

Dans cette liste, tu peux mettre n'importe quel objet qui implémente IBalanceReporter : SavingsAccount, CheckingAccount, même un MockAccountForTesting. Pas de magie, ça marche direct.

Schéma : ce que donne un contrat d'interface


+-------------------+      implémente     +-------------------+
|   BankAccount     |  <---------------- |  IBalanceReporter |
|  (SavingsAccount) |                    |-------------------|
|  (CheckingAccount)|                    | + ReportBalance() |
+-------------------+                    +-------------------+
         |                                   ^
         |                                   |
         +-----------+-----------------------+
                     |
        N'importe quelle autre classe qui implémente le contrat
Schéma : le contrat d'interface comme point d'interaction

5. Contrats, logique métier et design d'architecture

Le contrat d'interface sépare clairement ce que doit faire une classe de comment elle le fait. C'est pour ça que les architectes logiciels aiment designer la logique système via des interfaces. Souvent, on fait d'abord l'interface (le contrat), et l'implémentation vient après.

Exemple de la vraie vie : systèmes de paiement

Portefeuilles électroniques, cartes, PayPal, crypto... Chacun a ses détails, mais si tu abstrais et fais une interface IPaymentProvider :


public interface IPaymentProvider
{
    void Pay(decimal amount);
    bool Refund(decimal amount);
}

Le code qui bosse avec cette interface s'en fiche de savoir si ça paye par carte ou par compte. C'est pratique pour l'archi et la vie : tu peux ajouter de nouveaux systèmes de paiement sans toucher au reste du code.

6. Extraire la logique métier dans le contrat

Le contrat permet de sortir les règles métier principales au niveau de l'interface, et de laisser les détails (genre vérif de limite, cashback, etc.) aux classes concrètes.

Encore un exemple


public interface ILogger
{
    void LogInfo(string message);
    void LogError(string message);
}

public class ConsoleLogger : ILogger
{
    public void LogInfo(string message)  => Console.WriteLine($"INFO : {message}");
    public void LogError(string message) => Console.WriteLine($"ERREUR : {message}");
}

public class FileLogger : ILogger
{
    public void LogInfo(string message)  => /* Écriture dans le fichier */;
    public void LogError(string message) => /* Écriture dans le fichier */;
}

Ensuite, le code client ressemble à ça (peu importe avec quel logger il bosse) :


void DoWork(ILogger logger)
{
    logger.LogInfo("Le boulot a commencé.");
    // ... du taf ...
    logger.LogError("Le boulot a foiré.");
}

Voilà, c'est ça programmer au niveau des interfaces : le code client dépend juste du contrat (l'interface), pas de l'implémentation concrète.

7. Erreurs et pièges classiques

Les débutants font souvent l'erreur suivante : leur code est trop lié à des classes concrètes, pas aux interfaces. Résultat, plein de galères :

  • Difficile de remplacer ou de tester — faut réécrire plein de code.
  • Impossible d'ajouter de nouveaux types sans toucher à plein d'endroits.
  • Les modules sont trop «collés» entre eux.

À l'inverse — si tu construis ton système sur des interfaces dès le début et que tu passes les paramètres via elles, tu obtiens un code flexible et faiblement couplé.

Le conseil principal : pense à l'interaction entre modules comme une interaction entre contrats, pas entre implémentations concrètes.

2
Mission
C# SELF, niveau 23, leçon 3
Bloqué
Programmation au niveau des interfaces
Programmation au niveau des interfaces
2
Mission
C# SELF, niveau 23, leçon 3
Bloqué
Gestionnaire universel via des interfaces
Gestionnaire universel via des interfaces
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION