Adaptermuster
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 97 von 104.
Das Adapter-Muster ermöglicht es, dass inkompatible Schnittstellen zusammenarbeiten. Es fungiert als Brücke zwischen zwei Klassen und wandelt die Schnittstelle einer Klasse in eine Schnittstelle um, die Clients erwarten. Dies ist besonders nützlich bei der Integration von Legacy-Code oder Drittanbieterbibliotheken, deren Schnittstelle nicht mit der Ihres Systems übereinstimmt.
Stell dir vor, du hast eine bestehende Klasse, die genau das tut, was du benötigst, aber ihre Schnittstelle nicht dem entspricht, was dein Code erwartet. Anstatt eine der beiden Seiten zu ändern, erstellst du einen Adapter, der die bestehende Klasse umschließt und Aufrufe übersetzt:
#include <iostream>
#include <string>
// Bestehende Klasse mit inkompatibler Schnittstelle
class LegacyPrinter {
public:
void printMessage(const std::string& msg) {
std::cout << "Legacy: " << msg << "\n";
}
};
// Zielschnittstelle, die der Client erwartet
class Printer {
public:
virtual void print(const std::string& text) = 0;
virtual ~Printer() = default;
};
// Adapter umhüllt LegacyPrinter und implementiert die Printer-Schnittstelle
class PrinterAdapter : public Printer {
LegacyPrinter& legacy;
public:
PrinterAdapter(LegacyPrinter& lp) : legacy(lp) {}
void print(const std::string& text) override {
legacy.printMessage(text); // Übersetze den Aufruf
}
};
int main() {
LegacyPrinter oldPrinter;
PrinterAdapter adapter(oldPrinter);
// Client verwendet die erwartete Schnittstelle
adapter.print("Hello World");
}Der Adapter enthält eine Referenz auf LegacyPrinter und implementiert die Printer-Schnittstelle. Wenn print() aufgerufen wird, delegiert er an printMessage(). Der Clientcode arbeitet mit der Printer-Schnittstelle, ohne die darunterliegende Legacy-Implementierung zu kennen.
Verwende den Adapter, wenn du eine vorhandene Klasse verwenden möchtest, ihre Schnittstelle jedoch nicht dem entspricht, was du benötigst, oder wenn du eine wiederverwendbare Klasse erstellen musst, die mit Klassen mit inkompatiblen Schnittstellen zusammenarbeitet.
Aufgabe
EinfachLass uns ein Media Player Adapter-System entwickeln, das verschiedene Audio-Bibliotheksschnittstellen verbindet. Stell dir vor, du integrierst eine veraltete Audiobibliothek in eine moderne Media-Player-Anwendung: Die alte Bibliothek funktioniert perfekt, aber ihre Schnittstelle entspricht nicht den Erwartungen deines Players. Genau hier spielt das Adapter-Muster seine Stärke aus.
Du wirst deinen Code auf vier Dateien verteilen:
MediaPlayer.h: Definiere die Zielschnittstelle, die dein moderner Media Player erwartet.Erstelle eine abstrakte
MediaPlayer-Klasse mit diesen rein virtuellen Methoden:play(const std::string& filename): spielt die angegebene Datei abstop(): beendet die WiedergabesetVolume(int level): legt die Lautstärke fest (0–100)
Füge einen virtuellen Destruktor hinzu.
LegacyAudioLib.h: Erstelle die bestehende „Legacy“-Audiobibliothek mit einer inkompatiblen Schnittstelle.Deine
LegacyAudioLib-Klasse repräsentiert eine alte Bibliothek, die anders funktioniert als von deinem Player erwartet. Sie sollte Folgendes enthalten:loadAudio(const std::string& path): gibtLegacy: Loading [path]ausstartPlayback(): gibtLegacy: Playback startedausstopPlayback(): gibtLegacy: Playback stoppedausadjustVolume(double percentage): erwartet eine Dezimalzahl (0.0 bis 1.0) und gibtLegacy: Volume set to [percentage]aus (zeige den Dezimalwert an)
AudioAdapter.h: Erstelle den Adapter, der die beiden Schnittstellen verbindet.Deine
AudioAdapter-Klasse sollte vonMediaPlayererben und eine Referenz auf einLegacyAudioLib-Objekt halten. Der Adapter übersetzt Aufrufe der modernen Schnittstelle in Aufrufe der Legacy-Schnittstelle:play()sollte sowohlloadAudio()als auchstartPlayback()für die Legacy-Bibliothek aufrufenstop()sollte anstopPlayback()delegierensetVolume()erhält eine Ganzzahl (0–100), aber die Legacy-Bibliothek erwartet eine Dezimalzahl (0.0–1.0), daher musst du sie umwandeln
main.cpp: Zeige den Adapter in Aktion.Lies zwei Eingaben ein:
- Einen abzuspielenden Dateinamen (String)
- Einen Lautstärkepegel (Ganzzahl, 0–100)
Erstelle eine
LegacyAudioLib-Instanz und einenAudioAdapter, der sie umschließt. Verwende anschließend den Adapter über dieMediaPlayer-Schnittstelle (speichere ihn alsMediaPlayer-Zeiger), um:- Die Lautstärke auf den Eingabepegel zu setzen
- Den eingegebenen Dateinamen abzuspielen
- Die Wiedergabe zu beenden
Beispielsweise mit den Eingaben song.mp3 und 75:
Legacy: Volume set to 0.75
Legacy: Loading song.mp3
Legacy: Playback started
Legacy: Playback stoppedMit den Eingaben podcast.wav und 50:
Legacy: Volume set to 0.5
Legacy: Loading podcast.wav
Legacy: Playback started
Legacy: Playback stoppedBeachte, dass der Client-Code vollständig mit der MediaPlayer-Schnittstelle arbeitet. Er weiß nicht, dass im Hintergrund tatsächlich eine Legacy-Bibliothek die Arbeit erledigt. Der Adapter übernimmt die gesamte Übersetzung, einschließlich der Umwandlung der ganzzahligen Lautstärke in eine Dezimalzahl. Das ist das Wesentliche des Adapter-Musters: inkompatible Schnittstellen zusammenarbeiten zu lassen, ohne eine der beiden Seiten zu verändern.
Probier es selbst
#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "LegacyAudioLib.h"
#include "AudioAdapter.h"
using namespace std;
int main() {
// Eingaben lesen
string filename;
int volumeLevel;
cin >> filename;
cin >> volumeLevel;
// TODO: Erstelle eine LegacyAudioLib-Instanz
// TODO: Erstelle einen AudioAdapter, der die Legacy-Bibliothek umhüllt
// TODO: Speichere den Adapter als MediaPlayer-Zeiger
// TODO: Verwende die MediaPlayer-Schnittstelle, um:
// 1. Setze die Lautstärke auf den Eingabepegel
// 2. Spiele den Eingabe-Dateinamen ab
// 3. Stoppe die Wiedergabe
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator11Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungMixins mittels CRTPPimpl-IdiomTyplöschungEnum-Klassen & starke TypisierungAusnahmebehandlung in OOPBenutzerdefinierte Ausnahmehierarchien14Entwurfsmuster Teil 2
BefehlsmusterAdaptermusterDekorierermusterTemplate-Method-MusterZustandsmusterKompositmusterRAII als Muster3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler