Menu
Coddy logo textTech

Wzorzec adaptera

Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 97 z 104.

Wzorzec Adapter pozwala współpracować niekompatybilnym interfejsom. Działa jako most między dwiema klasami, przekształcając interfejs jednej klasy w interfejs oczekiwany przez klientów. Jest to szczególnie przydatne podczas integrowania starszego kodu lub bibliotek innych firm, które nie pasują do interfejsu Twojego systemu.

Wyobraź sobie, że masz istniejącą klasę, która robi to, czego potrzebujesz, ale jej interfejs nie pasuje do tego, czego oczekuje twój kod. Zamiast modyfikować którąkolwiek ze stron, tworzysz adapter, który opakowuje istniejącą klasę i tłumaczy wywołania:

#include <iostream>
#include <string>

// Istniejąca klasa z niekompatybilnym interfejsem
class LegacyPrinter {
public:
    void printMessage(const std::string& msg) {
        std::cout << "Legacy: " << msg << "\n";
    }
};

// Docelowy interfejs, którego oczekuje klient
class Printer {
public:
    virtual void print(const std::string& text) = 0;
    virtual ~Printer() = default;
};

// Adapter opakowuje LegacyPrinter i implementuje interfejs Printer
class PrinterAdapter : public Printer {
    LegacyPrinter& legacy;
public:
    PrinterAdapter(LegacyPrinter& lp) : legacy(lp) {}
    
    void print(const std::string& text) override {
        legacy.printMessage(text);  // Dostosuj wywołanie
    }
};

int main() {
    LegacyPrinter oldPrinter;
    PrinterAdapter adapter(oldPrinter);
    
    // Klient korzysta z oczekiwanego interfejsu
    adapter.print("Hello World");
}

Adapter przechowuje referencję do LegacyPrinter i implementuje interfejs Printer. Gdy wywoływana jest metoda print(), adapter deleguje wywołanie do printMessage(). Kod klienta korzysta z interfejsu Printer, nie wiedząc, jaka starsza implementacja kryje się pod spodem.

Użyj wzorca Adapter, gdy chcesz skorzystać z istniejącej klasy, ale jej interfejs nie odpowiada Twoim potrzebom, lub gdy chcesz utworzyć klasę wielokrotnego użytku, która współpracuje z klasami mającymi niekompatybilne interfejsy.

challenge icon

Wyzwanie

Łatwy

Zbudujmy system adaptera odtwarzacza multimediów, który łączy różne interfejsy bibliotek audio. Wyobraź sobie, że integrujesz starszą bibliotekę audio z nowoczesną aplikacją odtwarzacza multimediów: stara biblioteka działa bez zarzutu, ale jej interfejs nie pasuje do tego, czego oczekuje Twój odtwarzacz. Właśnie w takich sytuacjach wzorzec Adapter sprawdza się najlepiej.

Rozmieścisz kod w czterech plikach:

  • MediaPlayer.h: Zdefiniuj interfejs docelowy, którego oczekuje nowoczesny odtwarzacz multimediów.

    Utwórz abstrakcyjną klasę MediaPlayer z następującymi czysto wirtualnymi metodami:

    • play(const std::string& filename): odtwarza wskazany plik
    • stop(): zatrzymuje odtwarzanie
    • setVolume(int level): ustawia głośność (0-100)

    Dodaj wirtualny destruktor.

  • LegacyAudioLib.h: Utwórz istniejącą „starszą” bibliotekę audio z niezgodnym interfejsem.

    Klasa LegacyAudioLib reprezentuje starą bibliotekę, która działa inaczej, niż oczekuje Twój odtwarzacz. Powinna mieć:

    • loadAudio(const std::string& path): wyświetla Legacy: Loading [path]
    • startPlayback(): wyświetla Legacy: Playback started
    • stopPlayback(): wyświetla Legacy: Playback stopped
    • adjustVolume(double percentage): przyjmuje wartość dziesiętną (od 0.0 do 1.0) i wyświetla Legacy: Volume set to [percentage] (pokaż wartość dziesiętną)
  • AudioAdapter.h: Zbuduj adapter, który łączy oba interfejsy.

    Klasa AudioAdapter powinna dziedziczyć po MediaPlayer i przechowywać referencję do obiektu LegacyAudioLib. Adapter tłumaczy wywołania nowoczesnego interfejsu na wywołania starszego interfejsu:

    • play() powinno wywołać zarówno loadAudio(), jak i startPlayback() w starszej bibliotece
    • stop() powinno przekazać wywołanie do stopPlayback()
    • setVolume() przyjmuje liczbę całkowitą (0-100), ale starsza biblioteka oczekuje wartości dziesiętnej (0.0-1.0), więc trzeba ją przeliczyć
  • main.cpp: Zademonstruj działanie adaptera.

    Wczytaj dwa dane wejściowe:

    1. Nazwę pliku do odtworzenia (ciąg znaków)
    2. Poziom głośności (liczba całkowita, 0-100)

    Utwórz instancję LegacyAudioLib oraz obiekt AudioAdapter, który ją opakowuje. Następnie użyj adaptera za pośrednictwem interfejsu MediaPlayer (przechowaj go jako wskaźnik MediaPlayer), aby:

    1. Ustawić głośność na wczytany poziom
    2. Odtworzyć wczytaną nazwę pliku
    3. Zatrzymać odtwarzanie

Na przykład dla danych wejściowych song.mp3 i 75:

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

Dla danych wejściowych podcast.wav i 50:

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

Zwróć uwagę, że kod klienta korzysta wyłącznie z interfejsu MediaPlayer. Nie wie, że całą pracę w tle wykonuje starsza biblioteka. Adapter obsługuje wszystkie tłumaczenia, w tym konwersję całkowitej wartości głośności na wartość dziesiętną. Na tym polega istota wzorca Adapter: umożliwia współpracę niezgodnych interfejsów bez modyfikowania żadnej ze stron.

Spróbuj swoich sił

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

using namespace std;

int main() {
    // Wczytaj dane wejściowe
    string filename;
    int volumeLevel;
    cin >> filename;
    cin >> volumeLevel;
    
    // TODO: Utwórz instancję LegacyAudioLib
    
    // TODO: Utwórz AudioAdapter, który opakowuje starszą bibliotekę
    
    // TODO: Przechowaj adapter jako wskaźnik do MediaPlayer
    
    // TODO: Użyj interfejsu MediaPlayer, aby:
    // 1. Ustawić głośność na poziom podany na wejściu
    // 2. Odtworzyć plik o nazwie podanej na wejściu
    // 3. Zatrzymać odtwarzanie
    
    return 0;
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C++ online