Menu
Coddy logo textTech

Patron Adaptateur

Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 97 sur 104.

Le pattern Adapter permet à des interfaces incompatibles de travailler ensemble. Il agit comme un pont entre deux classes, convertissant l'interface d'une classe en une interface attendue par les clients. Ceci est particulièrement utile lors de l'intégration de code hérité ou de bibliothèques tierces qui ne correspondent pas à l'interface de votre système.

Imaginez que vous ayez une classe existante qui fait ce dont vous avez besoin, mais son interface ne correspond pas à ce que votre code attend. Au lieu de modifier l'un ou l'autre côté, vous créez un adaptateur qui enveloppe la classe existante et traduit les appels :

#include <iostream>
#include <string>

// Classe existante avec une interface incompatible
class LegacyPrinter {
public:
    void printMessage(const std::string& msg) {
        std::cout << "Legacy: " << msg << "\n";
    }
};

// Interface cible attendue par le client
class Printer {
public:
    virtual void print(const std::string& text) = 0;
    virtual ~Printer() = default;
};

// L'adaptateur enveloppe LegacyPrinter et implémente l'interface Printer
class PrinterAdapter : public Printer {
    LegacyPrinter& legacy;
public:
    PrinterAdapter(LegacyPrinter& lp) : legacy(lp) {}
    
    void print(const std::string& text) override {
        legacy.printMessage(text);  // Traduire l'appel
    }
};

int main() {
    LegacyPrinter oldPrinter;
    PrinterAdapter adapter(oldPrinter);
    
    // Le client utilise l'interface attendue
    adapter.print("Hello World");
}

L'adaptateur détient une référence vers le LegacyPrinter et implémente l'interface Printer. Lorsque print() est appelée, il délègue à printMessage(). Le code client travaille avec l'interface Printer sans connaître l'implémentation héritée sous-jacente.

Utilisez l'Adaptateur lorsque vous souhaitez utiliser une classe existante mais que son interface ne correspond pas à ce dont vous avez besoin, ou lorsque vous devez créer une classe réutilisable qui coopère avec des classes ayant des interfaces incompatibles.

challenge icon

Défi

Facile

Construisons un système d'Adaptateur de Lecteur Multimédia qui fait le pont entre différentes interfaces de bibliothèques audio. Imaginez que vous intégriez une ancienne bibliothèque audio dans une application de lecteur multimédia moderne — l'ancienne bibliothèque fonctionne parfaitement, mais son interface ne correspond pas à ce que votre lecteur attend. C'est exactement là que le motif Adaptateur (Adapter pattern) brille.

Vous organiserez votre code sur quatre fichiers :

  • MediaPlayer.h : Définissez l'interface cible que votre lecteur multimédia moderne attend.

    Créez une classe abstraite MediaPlayer avec ces méthodes virtuelles pures :

    • play(const std::string& filename) — joue le fichier spécifié
    • stop() — arrête la lecture
    • setVolume(int level) — règle le volume (0-100)

    Incluez un destructeur virtuel.

  • LegacyAudioLib.h : Créez la bibliothèque audio "héritée" existante avec une interface incompatible.

    Votre classe LegacyAudioLib représente une ancienne bibliothèque qui fonctionne différemment de ce que votre lecteur attend. Elle devrait avoir :

    • loadAudio(const std::string& path) — affiche Legacy: Loading [path]
    • startPlayback() — affiche Legacy: Playback started
    • stopPlayback() — affiche Legacy: Playback stopped
    • adjustVolume(double percentage) — prend une valeur décimale (0.0 à 1.0) et affiche Legacy: Volume set to [percentage] (affiche la valeur décimale)
  • AudioAdapter.h : Construisez l'adaptateur qui fait le pont entre les deux interfaces.

    Votre classe AudioAdapter doit hériter de MediaPlayer et détenir une référence vers un objet LegacyAudioLib. L'adaptateur traduit les appels de l'interface moderne vers l'ancienne :

    • play() doit appeler à la fois loadAudio() et startPlayback() sur la bibliothèque héritée
    • stop() doit déléguer à stopPlayback()
    • setVolume() reçoit un entier (0-100) mais la bibliothèque héritée attend une valeur décimale (0.0-1.0), vous devrez donc la convertir
  • main.cpp : Démontrez l'adaptateur en action.

    Lisez deux entrées :

    1. Un nom de fichier à lire (chaîne de caractères)
    2. Un niveau de volume (entier, 0-100)

    Créez une instance de LegacyAudioLib et un AudioAdapter qui l'enveloppe. Utilisez ensuite l'adaptateur via l'interface MediaPlayer (stockez-le en tant que pointeur MediaPlayer) pour :

    1. Régler le volume au niveau saisi
    2. Lire le nom de fichier saisi
    3. Arrêter la lecture

Par exemple, avec les entrées song.mp3 et 75 :

Legacy: Volume set to 0.75
Legacy: Loading song.mp3
Legacy: Playback started
Legacy: Playback stopped

Avec les entrées podcast.wav et 50 :

Legacy: Volume set to 0.5
Legacy: Loading podcast.wav
Legacy: Playback started
Legacy: Playback stopped

Remarquez comment le code client travaille entièrement avec l'interface MediaPlayer — il n'a aucune idée qu'une bibliothèque héritée effectue le travail réel en dessous. L'adaptateur gère toute la traduction, y compris la conversion du volume entier en décimal. C'est l'essence même du motif Adaptateur : faire fonctionner ensemble des interfaces incompatibles sans modifier aucun des deux côtés.

Aide-mémoire

Le patron Adaptateur (Adapter pattern) permet à des interfaces incompatibles de fonctionner ensemble en agissant comme un pont entre deux classes. Il convertit l'interface d'une classe en une interface attendue par les clients.

Utilisez le patron Adaptateur lorsque vous souhaitez utiliser une classe existante mais que son interface ne correspond pas à vos besoins, ou lors de l'intégration de code hérité ou de bibliothèques tierces.

Structure

Le patron se compose de trois éléments principaux :

  • Interface cible (Target Interface) : L'interface attendue par le client
  • Adapté (Adaptee) : La classe existante avec une interface incompatible
  • Adaptateur (Adapter) : Enveloppe l'adapté et implémente l'interface cible, traduisant les appels entre les deux

Exemple

#include <iostream>
#include <string>

// Classe existante avec une interface incompatible (Adaptee)
class LegacyPrinter {
public:
    void printMessage(const std::string& msg) {
        std::cout << "Legacy: " << msg << "\n";
    }
};

// Interface cible attendue par le client
class Printer {
public:
    virtual void print(const std::string& text) = 0;
    virtual ~Printer() = default;
};

// L'adaptateur enveloppe LegacyPrinter et implémente l'interface Printer
class PrinterAdapter : public Printer {
    LegacyPrinter& legacy;
public:
    PrinterAdapter(LegacyPrinter& lp) : legacy(lp) {}
    
    void print(const std::string& text) override {
        legacy.printMessage(text);  // Traduire l'appel
    }
};

int main() {
    LegacyPrinter oldPrinter;
    PrinterAdapter adapter(oldPrinter);
    
    // Le client utilise l'interface attendue
    adapter.print("Hello World");
}

L'adaptateur détient une référence vers l'adapté et implémente l'interface cible. Lorsque des méthodes sont appelées sur l'adaptateur, il délègue aux méthodes de l'adapté, traduisant les appels si nécessaire. Le code client travaille avec l'interface cible sans connaître l'implémentation héritée.

Essayez vous-même

#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "LegacyAudioLib.h"
#include "AudioAdapter.h"

using namespace std;

int main() {
    // Lire les entrées
    string filename;
    int volumeLevel;
    cin >> filename;
    cin >> volumeLevel;
    
    // TODO: Créer une instance de LegacyAudioLib
    
    // TODO: Créer un AudioAdapter qui enveloppe la bibliothèque héritée (legacy library)
    
    // TODO: Stocker l'adaptateur en tant que pointeur MediaPlayer
    
    // TODO: Utiliser l'interface MediaPlayer pour :
    // 1. Régler le volume au niveau saisi
    // 2. Lire le fichier filename saisi
    // 3. Arrêter la lecture
    
    return 0;
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet