Паттерны » Структурные паттерны » Adapter
Паттерн Adapter (адаптер, wrapper, обертка)
Назначение паттерна Adapter
Часто в новом программном проекте не удается повторно использовать уже существующий код. Например, имеющиеся классы могут обладать нужной функциональностью, но иметь при этом несовместимые интерфейсы. В таких случаях следует использовать паттерн Adapter (адаптер).
Паттерн Adapter, представляющий собой программную обертку над существующими классами, преобразует их интерфейсы к виду, пригодному для последующего использования.
Рассмотрим простой пример, когда следует применять паттерн Adapter. Пусть мы разрабатываем систему климат-контроля, предназначенной для автоматического поддержания температуры окружающего пространства в заданных пределах. Важным компонентом такой системы является температурный датчик, с помощью которого измеряют температуру окружающей среды для последующего анализа. Для этого датчика уже имеется готовое программное обеспечение от сторонних разработчиков, представляющее собой некоторый класс с соответствующим интерфейсом. Однако использовать этот класс непосредственно не удастся, так как показания датчика снимаются в градусах Фаренгейта. Нужен адаптер, преобразующий температуру в шкалу Цельсия.
Контейнеры queue, priority_queue и stack библиотеки стандартных шаблонов STL реализованы на базе последовательных контейнеров list, deque и vector, адаптируя их интерфейсы к нужному виду. Именно поэтому эти контейнеры называют контейнерами-адаптерами.
Описание паттерна Adapter
Пусть класс, интерфейс которого нужно адаптировать к нужному виду, имеет имя Adaptee. Для решения задачи преобразования его интерфейса паттерн Adapter вводит следующую иерархию классов:
- Виртуальный базовый класс Target. Здесь объявляется пользовательский интерфейс подходящего вида. Только этот интерфейс доступен для пользователя.
- Производный класс Adapter, реализующий интерфейс Target. В этом классе также имеется указатель или ссылка на экземпляр Adaptee. Паттерн Adapter использует этот указатель для перенаправления клиентских вызовов в Adaptee. Так как интерфейсы Adaptee и Target несовместимы между собой, то эти вызовы обычно требуют преобразования.
UML-диаграмма классов паттерна Adapter
Реализация паттерна Adapter
Классическая реализация паттерна Adapter
Приведем реализацию паттерна Adapter. Для примера выше адаптируем показания температурного датчика системы климат-контроля, переведя их из градусов Фаренгейта в градусы Цельсия (предполагается, что код этого датчика недоступен для модификации).
#include <iostream> // Уже существующий класс температурного датчика окружающей среды class FahrenheitSensor { public: // Получить показания температуры в градусах Фаренгейта float getFahrenheitTemp() { float t = 32.0; // ... какой то код return t; } }; class Sensor { public: virtual ~Sensor() {} virtual float getTemperature() = 0; }; class Adapter : public Sensor { public: Adapter( FahrenheitSensor* p ) : p_fsensor(p) { } ~Adapter() { delete p_fsensor; } float getTemperature() { return (p_fsensor->getFahrenheitTemp()-32.0)*5.0/9.0; } private: FahrenheitSensor* p_fsensor; }; int main() { Sensor* p = new Adapter( new FahrenheitSensor); cout << "Celsius temperature = " << p->getTemperature() << endl; delete p; return 0; }
Реализация паттерна Adapter на основе закрытого наследования
Пусть наш температурный датчик системы климат-контроля поддерживает функцию юстировки для получения более точных показаний. Эта функция не является обязательной для использования, возможно, поэтому соответствующий метод adjust() объявлен разработчиками защищенным в существующем классе FahrenheitSensor.
Разрабатываемая нами система должна поддерживать настройку измерений. Так как доступ к защищенному методу через указатель или ссылку запрещен, то классическая реализация паттерна Adapter здесь уже не подходит. Единственное решение - наследовать от класса FahrenheitSensor. Интерфейс этого класса должен оставаться недоступным пользователю, поэтому наследование должно быть закрытым.
Цели, преследуемые при использовании открытого и закрытого наследования различны. Если открытое наследование применяется для наследования интерфейса и реализации, то закрытое наследование - только для наследования реализации.
#include <iostream> class FahrenheitSensor { public: float getFahrenheitTemp() { float t = 32.0; // ... return t; } protected: void adjust() {} // Настройка датчика (защищенный метод) }; class Sensor { public: virtual ~Sensor() {} virtual float getTemperature() = 0; virtual void adjust() = 0; }; class Adapter : public Sensor, private FahrenheitSensor { public: Adapter() { } float getTemperature() { return (getFahrenheitTemp()-32.0)*5.0/9.0; } void adjust() { FahrenheitSensor::adjust(); } }; int main() { Sensor * p = new Adapter(); p->adjust(); cout << "Celsius temperature = " << p->getTemperature() << endl; delete p; return 0; }
Результаты применения паттерна Adapter
Достоинства паттерна Adapter
- Паттерн Adapter позволяет повторно использовать уже имеющийся код, адаптируя его несовместимый интерфейс к виду, пригодному для использования.
Недостатки паттерна Adapter
- Задача преобразования интерфейсов может оказаться непростой в случае, если клиентские вызовы и (или) передаваемые параметры не имеют функционального соответствия в адаптируемом объекте.