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.
Défi
FacileConstruisons 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
MediaPlayeravec ces méthodes virtuelles pures :play(const std::string& filename)— joue le fichier spécifiéstop()— arrête la lecturesetVolume(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
LegacyAudioLibreprésente une ancienne bibliothèque qui fonctionne différemment de ce que votre lecteur attend. Elle devrait avoir :loadAudio(const std::string& path)— afficheLegacy: Loading [path]startPlayback()— afficheLegacy: Playback startedstopPlayback()— afficheLegacy: Playback stoppedadjustVolume(double percentage)— prend une valeur décimale (0.0 à 1.0) et afficheLegacy: Volume set to [percentage](affiche la valeur décimale)
AudioAdapter.h: Construisez l'adaptateur qui fait le pont entre les deux interfaces.Votre classe
AudioAdapterdoit hériter deMediaPlayeret détenir une référence vers un objetLegacyAudioLib. L'adaptateur traduit les appels de l'interface moderne vers l'ancienne :play()doit appeler à la foisloadAudio()etstartPlayback()sur la bibliothèque héritéestop()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 :
- Un nom de fichier à lire (chaîne de caractères)
- Un niveau de volume (entier, 0-100)
Créez une instance de
LegacyAudioLibet unAudioAdapterqui l'enveloppe. Utilisez ensuite l'adaptateur via l'interfaceMediaPlayer(stockez-le en tant que pointeurMediaPlayer) pour :- Régler le volume au niveau saisi
- Lire le nom de fichier saisi
- 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 stoppedAvec les entrées podcast.wav et 50 :
Legacy: Volume set to 0.5
Legacy: Loading podcast.wav
Legacy: Playback started
Legacy: Playback stoppedRemarquez 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;
}
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
1Fondamentaux de la POO
Fichiers externesBuild et Compilation C++Fichiers d'en-tête et Fichiers sourceEspaces de noms et PortéeIntroduction à la POO en C++Classes vs ObjetsLe pointeur 'this'Méthodes (Fonctions membres)Attributs (Membres de données)Bases des Ctors et DtorsRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d'instance vs membres statiquesGetters et SettersFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d'accès à l'héritageOrdre d'appel des Ctor et DtorRedéfinition de méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif - Hiérarchie des employés10Présentation de la STL
Présentation et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - Fréquence des mots13Patrons de conception, Partie 1
Introduction aux patrons de conceptionPatron SingletonFabrique & Fabrique abstraitePatron MonteurPatron ObservateurPatron Stratégie2Gestion de la mémoire
Mémoire Pile vs TasPointeurs et RéférencesMémoire dynamique (new/delete)Smart Pointers en C++RAII en C++Récapitulatif - Gestionnaire de tableaux dynamiques5Encapsulation
Spécificateurs d'accès en C++Spécificateurs d'accès en profondeurMasquage d'informationStruct vs ClassClasses imbriquées et internesRécapitulatif - Système de gestion d'étudiants8Polymorphisme
Polymorphisme : Compilation vs ExécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d'interfaces en C++Dynamic Casting & RTTIRécapitulatif - Calculateur de formes11Concepts avancés de la POO
Composition vs HéritageMixins via CRTPIdiome PimplEffacement de typeEnum Classes & Typage fortGestion des exceptions en POOHiérarchies d'exceptions personnalisées14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeRAII en tant que patron3Constructeurs et Destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d'initialisation du constructeurConstructeurs déléguésAnalyse approfondie du destructeurRègle de trois / cinq / zéroRécapitulatif - Classe String6Surcharge d'opérateurs
Introduction à la surcharge d'opérateursSurcharge des opérateurs arithmétiquesSurcharge des opérateurs de comparaisonOpérateurs de fluxSurcharge de l'opérateur d'affectationSurcharge des opérateurs [] et ()Opérateurs de conversion de typeRécapitulatif - Classe Matrix9Templates
Templates de fonctionsTemplates de classesSpécialisation de templatesTemplates variadiquesBases de SFINAE & Type TraitsRécapitulatif - Conteneur générique