Aller au contenu

Adapter

Design Pattern Adapter

Un pont entre deux interfaces

Introduction

Le design pattern Adapter est un pattern de conception de structuration dont l'objectif est de rendre compatibles deux interfaces (pas interface au sens C#, mais au sens conceptuel). Il joue le rôle d'un intermédiaire entre deux classes en traduisant les appels de l'interface attendue par l'application vers celle réellement fournie par un composant existant. Cette approche permet de réutiliser du code sans modifier son implémentation.

Ce design pattern est particulièrement utile lors de l'intégration d'une bibliothèque tierce ou d'un service externe dont l'interface diffère de celle attendue par l'application.

Problématique

Imaginons une application envoyant des mails. Le code s'appuie sur une interface unique :

public interface INotificationSender
{
    Task SendAsync(string recipient, string message);
}

Dans le code de notre application, nous utilisons cette abstraction pour envoyer un e-mail :

  await notificationSender.SendAsync(
    "contact@algowin.fr",
    "Votre commande a été expédiée.");

Quelques mois plus tard, il est demandé d'intégrer une bibliothèque tierce permettant l'envoi d'e-mails et nous voyons qu'il est nécessaire d'utiliser cette interface :

public class ThirdPartyEmailService
{
    public Task SendEmailAsync(string email, string subject, string body)
    {
        // Implémentation.
    }
}

Elle est différente de celle utilisée par notre application :

  • Le contrat proposé par la bibliothèque est différent de celui attendu par l'application.

  • Les méthodes SendAsync et SendEmailAsync ont des signatures différentes.

Modifier directement notre code pour utiliser cette bibliothèque créerait un fort couplage avec son implémentation. Le design pattern Adapter permet de conserver l'interface attendue par l'application tout en adaptant les appels vers la bibliothèque tierce.

Fonctionnement

Le concept fondamental du design pattern Adapter est d'implémenter une classe intermédiaire qui implémente l'interface attendue par l'application tout en encapsulant l'interface ou classe existante.

Notre code continuera donc d'utiliser l'interface INotificationSender, tandis que l'adapter traduit les appels vers la bibliothèque externe :

Design Pattern Adapter : les acteurs

Le code métier de l’application ne dépend pas directement de la bibliothèque tierce. Cette dépendance est isolée dans l’adapter, qui traduit les appels de méthodes vers la bibliothèque tierce.

Implémentation

L'adapter EmailAdapter implémente l'interface que nous utilisons dans notre application :

public class EmailAdapter : INotificationSender
{
    private readonly ThirdPartyEmailService _service;

    public EmailAdapter(ThirdPartyEmailService service)
    {
        _service = service;
    }

    public Task SendAsync(string recipient, string message)
    {
        return _service.SendEmailAsync(recipient, "Notification", message);
    }
}

Le code que nous avions écrit dans notre application initialement n'est pas modifié :

await notificationSender.SendAsync(
    "contact@algowin.fr",
    "Votre commande a été expédiée.");

L'adapter se charge de convertir l'appel vers la bibliothèque tierce.

Enregistrement avec l'injection de dépendances

L'application peut enregistrer les services dans le conteneur d'injection de dépendances (nouvelle classe envoyant des e-mails et notre adapter) :

builder.Services.AddSingleton<ThirdPartyEmailService>();

builder.Services.AddScoped<INotificationSender, EmailAdapter>();

Toutes les classes dépendant de l'interface INotificationSender utilisent désormais automatiquement l'adapter.

Avantages

Le design pattern Adapter présente plusieurs avantages :

  • D'un point de vue de la conception, il favorise les principes SOLID OCP (Open/Closed Principle) et DIP (Dependency Invertion Principle).

  • Il permet de réutiliser du code existant sans le modifier.

  • Il réduit le couplage entre le code et les bibliothèques / services externes.

  • Il facilite les tests unitaires grâce à l'utilisation d'interfaces.

  • Il facilite le remplacement d'une bibliothèque ou d'un service externe sans modifier le code de l'application.