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.
Wyzwanie
ŁatwyZbudujmy 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ę
MediaPlayerz następującymi czysto wirtualnymi metodami:play(const std::string& filename): odtwarza wskazany plikstop(): zatrzymuje odtwarzaniesetVolume(int level): ustawia głośność (0-100)
Dodaj wirtualny destruktor.
LegacyAudioLib.h: Utwórz istniejącą „starszą” bibliotekę audio z niezgodnym interfejsem.Klasa
LegacyAudioLibreprezentuje starą bibliotekę, która działa inaczej, niż oczekuje Twój odtwarzacz. Powinna mieć:loadAudio(const std::string& path): wyświetlaLegacy: Loading [path]startPlayback(): wyświetlaLegacy: Playback startedstopPlayback(): wyświetlaLegacy: Playback stoppedadjustVolume(double percentage): przyjmuje wartość dziesiętną (od 0.0 do 1.0) i wyświetlaLegacy: Volume set to [percentage](pokaż wartość dziesiętną)
AudioAdapter.h: Zbuduj adapter, który łączy oba interfejsy.Klasa
AudioAdapterpowinna dziedziczyć poMediaPlayeri przechowywać referencję do obiektuLegacyAudioLib. Adapter tłumaczy wywołania nowoczesnego interfejsu na wywołania starszego interfejsu:play()powinno wywołać zarównoloadAudio(), jak istartPlayback()w starszej bibliotecestop()powinno przekazać wywołanie dostopPlayback()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:
- Nazwę pliku do odtworzenia (ciąg znaków)
- Poziom głośności (liczba całkowita, 0-100)
Utwórz instancję
LegacyAudioLiboraz obiektAudioAdapter, który ją opakowuje. Następnie użyj adaptera za pośrednictwem interfejsuMediaPlayer(przechowaj go jako wskaźnikMediaPlayer), aby:- Ustawić głośność na wczytany poziom
- Odtworzyć wczytaną nazwę pliku
- 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 stoppedDla danych wejściowych podcast.wav i 50:
Legacy: Volume set to 0.5
Legacy: Loading podcast.wav
Legacy: Playback started
Legacy: Playback stoppedZwróć 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;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów11Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieMixiny z użyciem CRTPIdiom PimplWymazywanie typówKlasy enum i silne typowanieObsługa wyjątków w OOPWłasne hierarchie wyjątków14Wzorce projektowe, część 2
Wzorzec poleceniaWzorzec adapteraWzorzec dekoratoraWzorzec metody szablonowejWzorzec stanuWzorzec kompozytuRAII jako wzorzec3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online