Menu
Coddy logo textTech

Паттерн Адаптер

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 97 из 104.

Паттерн Адаптер (Adapter pattern) позволяет несовместимым интерфейсам работать вместе. Он выступает в качестве моста между двумя классами, преобразуя интерфейс одного класса в интерфейс, который ожидают клиенты. Это особенно полезно при интеграции устаревшего кода или сторонних библиотек, которые не соответствуют интерфейсу вашей системы.

Представьте, что у вас есть существующий класс, который делает то, что вам нужно, но его интерфейс не соответствует тому, что ожидает ваш код. Вместо того чтобы изменять какую-либо из сторон, вы создаете адаптер, который оборачивает существующий класс и транслирует вызовы:

#include <iostream>
#include <string>

// Существующий класс с несовместимым интерфейсом
class LegacyPrinter {
public:
    void printMessage(const std::string& msg) {
        std::cout << "Legacy: " << msg << "\n";
    }
};

// Целевой интерфейс, который ожидает клиент
class Printer {
public:
    virtual void print(const std::string& text) = 0;
    virtual ~Printer() = default;
};

// Адаптер оборачивает LegacyPrinter и реализует интерфейс Printer
class PrinterAdapter : public Printer {
    LegacyPrinter& legacy;
public:
    PrinterAdapter(LegacyPrinter& lp) : legacy(lp) {}
    
    void print(const std::string& text) override {
        legacy.printMessage(text);  // Трансляция вызова
    }
};

int main() {
    LegacyPrinter oldPrinter;
    PrinterAdapter adapter(oldPrinter);
    
    // Клиент использует ожидаемый интерфейс
    adapter.print("Hello World");
}

Адаптер хранит ссылку на LegacyPrinter и реализует интерфейс Printer. При вызове print() он делегирует выполнение методу printMessage(). Клиентский код работает с интерфейсом Printer, не зная о лежащей в основе устаревшей реализации.

Используйте Адаптер, когда вы хотите использовать существующий класс, но его интерфейс не соответствует вашим потребностям, или когда вам нужно создать повторно используемый класс, который взаимодействует с классами, имеющими несовместимые интерфейсы.

challenge icon

Задание

Легко

Давайте создадим систему Media Player Adapter, которая связывает различные интерфейсы аудиобиблиотек. Представьте, что вы интегрируете устаревшую аудиобиблиотеку в современное приложение медиаплеера — старая библиотека работает отлично, но её интерфейс не соответствует тому, что ожидает ваш плеер. Это именно та ситуация, где паттерн Адаптер (Adapter) проявляет себя наилучшим образом.

Вы организуете свой код в четырех файлах:

  • MediaPlayer.h: Определите целевой интерфейс, который ожидает ваш современный медиаплеер.

    Создайте абстрактный класс MediaPlayer со следующими чисто виртуальными методами:

    • play(const std::string& filename) — воспроизводит указанный файл
    • stop() — останавливает воспроизведение
    • setVolume(int level) — устанавливает громкость (0-100)

    Включите виртуальный деструктор.

  • LegacyAudioLib.h: Создайте существующую «устаревшую» аудиобиблиотеку с несовместимым интерфейсом.

    Ваш класс LegacyAudioLib представляет собой старую библиотеку, которая работает иначе, чем ожидает ваш плеер. Он должен содержать:

    • loadAudio(const std::string& path) — выводит "Legacy: Loading [path]"
    • startPlayback() — выводит "Legacy: Playback started"
    • stopPlayback() — выводит "Legacy: Playback stopped"
    • adjustVolume(double percentage) — принимает десятичное число (от 0.0 до 1.0) и выводит "Legacy: Volume set to [percentage]" (покажите десятичное значение)
  • AudioAdapter.h: Создайте адаптер, который связывает два интерфейса.

    Ваш класс AudioAdapter должен наследоваться от MediaPlayer и хранить ссылку на объект LegacyAudioLib. Адаптер транслирует вызовы из современного интерфейса в устаревший:

    • play() должен вызывать и loadAudio(), и startPlayback() в устаревшей библиотеке
    • stop() должен делегировать выполнение методу stopPlayback()
    • setVolume() получает целое число (0-100), но устаревшая библиотека ожидает десятичное число (0.0-1.0), поэтому вам нужно будет его преобразовать
  • main.cpp: Продемонстрируйте работу адаптера.

    Прочитайте два входных значения:

    1. Имя файла для воспроизведения (строка)
    2. Уровень громкости (целое число, 0-100)

    Создайте экземпляр LegacyAudioLib и AudioAdapter, который его оборачивает. Затем используйте адаптер через интерфейс MediaPlayer (сохраните его как указатель MediaPlayer*), чтобы:

    1. Установить громкость на введенный уровень
    2. Воспроизвести введенное имя файла
    3. Остановить воспроизведение

Например, при входных данных "song.mp3" и 75:

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

При входных данных "podcast.wav" и 50:

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

Обратите внимание, как клиентский код полностью работает с интерфейсом MediaPlayer — он и не подозревает, что на самом деле всю работу выполняет устаревшая библиотека. Адаптер берет на себя всю трансляцию, включая преобразование целочисленной громкости в десятичную. В этом и заключается суть паттерна Адаптер: заставить несовместимые интерфейсы работать вместе без изменения кода обеих сторон.

Попробуйте сами

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

using namespace std;

int main() {
    // Чтение входных данных
    string filename;
    int volumeLevel;
    cin >> filename;
    cin >> volumeLevel;
    
    // TODO: Создать экземпляр LegacyAudioLib
    
    // TODO: Создать AudioAdapter, который оборачивает устаревшую библиотеку
    
    // TODO: Сохранить адаптер как указатель MediaPlayer
    
    // TODO: Использовать интерфейс MediaPlayer, чтобы:
    // 1. Установить громкость на входной уровень
    // 2. Воспроизвести файл с входным именем filename
    // 3. Остановить воспроизведение
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование