Паттерн Адаптер
Часть раздела Объектно-ориентированное программирование путешествия по 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, не зная о лежащей в основе устаревшей реализации.
Используйте Адаптер, когда вы хотите использовать существующий класс, но его интерфейс не соответствует вашим потребностям, или когда вам нужно создать повторно используемый класс, который взаимодействует с классами, имеющими несовместимые интерфейсы.
Задание
ЛегкоДавайте создадим систему 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: Продемонстрируйте работу адаптера.Прочитайте два входных значения:
- Имя файла для воспроизведения (строка)
- Уровень громкости (целое число, 0-100)
Создайте экземпляр
LegacyAudioLibиAudioAdapter, который его оборачивает. Затем используйте адаптер через интерфейсMediaPlayer(сохраните его как указательMediaPlayer*), чтобы:- Установить громкость на введенный уровень
- Воспроизвести введенное имя файла
- Остановить воспроизведение
Например, при входных данных "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;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер