Menu
Coddy logo textTech

Adapter-Muster

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 97 von 104.

Das Adapter-Muster ermöglicht es inkompatiblen Schnittstellen, zusammenzuarbeiten. 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 Bibliotheken von Drittanbietern, die nicht mit der Schnittstelle Ihres Systems übereinstimmen.

Stellen Sie sich vor, Sie haben eine bestehende Klasse, die genau das tut, was Sie benötigen, aber deren Schnittstelle nicht mit dem übereinstimmt, was Ihr Code erwartet. Anstatt eine der beiden Seiten zu ändern, erstellen Sie einen Adapter, der die bestehende Klasse umschließt und die Aufrufe übersetzt:

#include <iostream>
#include <string>

// Vorhandene Klasse mit inkompatibler Schnittstelle
class LegacyPrinter {
public:
    void printMessage(const std::string& msg) {
        std::cout << "Legacy: " << msg << "\n";
    }
};

// Ziel-Schnittstelle, die der Client erwartet
class Printer {
public:
    virtual void print(const std::string& text) = 0;
    virtual ~Printer() = default;
};

// Adapter kapselt 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);  // Den Aufruf weiterleiten
    }
};

int main() {
    LegacyPrinter oldPrinter;
    PrinterAdapter adapter(oldPrinter);
    
    // Client verwendet die erwartete Schnittstelle
    adapter.print("Hello World");
}

Der Adapter hält eine Referenz auf den LegacyPrinter und implementiert das Printer-Interface. Wenn print() aufgerufen wird, delegiert er an printMessage(). Der Client-Code arbeitet mit dem Printer-Interface, ohne etwas über die darunterliegende Legacy-Implementierung zu wissen.

Verwenden Sie den Adapter, wenn Sie eine bestehende Klasse verwenden möchten, deren Schnittstelle jedoch nicht Ihren Anforderungen entspricht, oder wenn Sie eine wiederverwendbare Klasse erstellen müssen, die mit Klassen mit inkompatiblen Schnittstellen zusammenarbeitet.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Media Player Adapter-System bauen, das verschiedene Audio-Bibliotheksschnittstellen überbrückt. Stellen Sie sich vor, Sie integrieren eine veraltete Audio-Bibliothek in eine moderne Media-Player-Anwendung – die alte Bibliothek funktioniert einwandfrei, aber ihre Schnittstelle entspricht nicht dem, was Ihr Player erwartet. Genau hier glänzt das Adapter-Muster.

Sie werden Ihren Code über vier Dateien organisieren:

  • MediaPlayer.h: Definieren Sie die Ziel-Schnittstelle, die Ihr moderner Media-Player erwartet.

    Erstellen Sie eine abstrakte MediaPlayer-Klasse mit diesen rein virtuellen Methoden:

    • play(const std::string& filename) — spielt die angegebene Datei ab
    • stop() — stoppt die Wiedergabe
    • setVolume(int level) — stellt die Lautstärke ein (0-100)

    Fügen Sie einen virtuellen Destruktor hinzu.

  • LegacyAudioLib.h: Erstellen Sie die bestehende "legacy" Audio-Bibliothek mit einer inkompatiblen Schnittstelle.

    Ihre LegacyAudioLib-Klasse repräsentiert eine alte Bibliothek, die anders funktioniert als Ihr Player erwartet. Sie sollte Folgendes haben:

    • loadAudio(const std::string& path) — gibt Legacy: Loading [path] aus
    • startPlayback() — gibt Legacy: Playback started aus
    • stopPlayback() — gibt Legacy: Playback stopped aus
    • adjustVolume(double percentage) — nimmt einen Dezimalwert (0.0 bis 1.0) entgegen und gibt Legacy: Volume set to [percentage] aus (zeigen Sie den Dezimalwert an)
  • AudioAdapter.h: Bauen Sie den Adapter, der die beiden Schnittstellen überbrückt.

    Ihre AudioAdapter-Klasse sollte von MediaPlayer erben und eine Referenz auf ein LegacyAudioLib-Objekt halten. Der Adapter übersetzt Aufrufe von der modernen Schnittstelle in die Legacy-Schnittstelle:

    • play() sollte sowohl loadAudio() als auch startPlayback() in der Legacy-Bibliothek aufrufen
    • stop() sollte an stopPlayback() delegieren
    • setVolume() erhält eine Ganzzahl (0-100), aber die Legacy-Bibliothek erwartet einen Dezimalwert (0.0-1.0), daher müssen Sie diesen umrechnen
  • main.cpp: Demonstrieren Sie den Adapter in Aktion.

    Lesen Sie zwei Eingaben ein:

    1. Einen Dateinamen zum Abspielen (String)
    2. Einen Lautstärkepegel (Ganzzahl, 0-100)

    Erstellen Sie eine LegacyAudioLib-Instanz und einen AudioAdapter, der diese umschließt. Verwenden Sie dann den Adapter über die MediaPlayer-Schnittstelle (speichern Sie ihn als MediaPlayer-Pointer), um:

    1. Die Lautstärke auf den eingegebenen Pegel einzustellen
    2. Den eingegebenen Dateinamen abzuspielen
    3. Die Wiedergabe zu stoppen

Zum Beispiel mit den Eingaben song.mp3 und 75:

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

Mit den Eingaben podcast.wav und 50:

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

Beachten Sie, wie der Client-Code vollständig mit der MediaPlayer-Schnittstelle arbeitet – er hat keine Ahnung, dass im Hintergrund eine Legacy-Bibliothek die eigentliche Arbeit erledigt. Der Adapter übernimmt die gesamte Übersetzung, einschließlich der Umrechnung der ganzzahligen Lautstärke in einen Dezimalwert. Dies ist der Kern des Adapter-Musters: inkompatible Schnittstellen zusammenarbeiten zu lassen, ohne eine der beiden Seiten zu verändern.

Spickzettel

Das Adapter-Muster ermöglicht es inkompatiblen Schnittstellen, zusammenzuarbeiten, indem es als Brücke zwischen zwei Klassen fungiert. Es wandelt die Schnittstelle einer Klasse in eine Schnittstelle um, die Clients erwarten.

Verwenden Sie das Adapter-Muster, wenn Sie eine bestehende Klasse nutzen möchten, deren Schnittstelle jedoch nicht Ihren Anforderungen entspricht, oder wenn Sie Legacy-Code oder Bibliotheken von Drittanbietern integrieren.

Struktur

Das Muster besteht aus drei Hauptkomponenten:

  • Target Interface: Die Schnittstelle, die der Client erwartet
  • Adaptee: Die bestehende Klasse mit einer inkompatiblen Schnittstelle
  • Adapter: Umhüllt den Adaptee und implementiert die Ziel-Schnittstelle, wobei Aufrufe zwischen ihnen übersetzt werden

Beispiel

#include <iostream>
#include <string>

// Bestehende Klasse mit inkompatibler Schnittstelle (Adaptee)
class LegacyPrinter {
public:
    void printMessage(const std::string& msg) {
        std::cout << "Legacy: " << msg << "\n";
    }
};

// Ziel-Schnittstelle, 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);  // Den Aufruf übersetzen
    }
};

int main() {
    LegacyPrinter oldPrinter;
    PrinterAdapter adapter(oldPrinter);
    
    // Client verwendet die erwartete Schnittstelle
    adapter.print("Hello World");
}

Der Adapter hält eine Referenz auf den Adaptee und implementiert die Ziel-Schnittstelle. Wenn Methoden auf dem Adapter aufgerufen werden, delegiert er an die Methoden des Adaptees und übersetzt die Aufrufe nach Bedarf. Der Client-Code arbeitet mit der Ziel-Schnittstelle, ohne von der Legacy-Implementierung zu wissen.

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 kapselt
    
    // TODO: Speichere den Adapter als MediaPlayer-Pointer
    
    // TODO: Nutze das MediaPlayer-Interface, um:
    // 1. Setze die Lautstärke auf das Eingabeniveau
    // 2. Spiele den eingegebenen Dateinamen ab
    // 3. Stoppe die Wiedergabe
    
    return 0;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung