Что такое паттерн адаптер
BestProg
Паттерн Адаптер. Обзор и исследование. Примеры реализации на языке C++
Содержание
Поиск на других ресурсах:
1. Назначение паттерна Адаптер (Adapter)
Паттерн Adapter принадлежит к структурным паттернам и используется для структурирования классов и объектов. Необходимость использования паттерна Adapter возникает в случаях, когда нужно привести (адаптировать) одну систему к требованиям другой системы.
Паттерн Adapter может выполнять не только простые преобразования (например, изменение имен), но и реализовывать совсем другой набор операций (подмена одного кода на другой).
2. В каких случаях применяется паттерн Адаптер? Случаи применения паттерна Адаптер
Паттерн Adapter применяется в следующих случаях:
3. Структура паттерна Адаптер (рисунок)
Паттерн Адаптер есть двух видов:
Существует два способа реализации класса Adapter :
Рисунок 1. Структура паттерна Adapter для класса
Рисунок 2. Структура паттерна Adapter для объекта
4. Пример реализации паттерна Adapter для класса на языке C++
В примере реализован адаптер для класса на языке C++, что отвечает рисунку 1 данной темы.
5. Пример реализации паттерна Adapter для объекта на языке C++
В примере, на языке C++ реализован паттерн Adapter для объекта, который соответствует рисунку 2 данной темы.
6. Результаты паттерна Adapter для класса. Примеры на языке C++
Если паттерн Адаптер применяется для класса (рисунок 1), то можно получить следующие результаты.
В нижеследующих примерах демонстрируются все перечисленные результаты паттерна Adapter для класса.
Пример 1. В примере демонстрируется невозможность реализовать паттерн Adapter для класса, если классы образовывают иерархию.
компилятор выдаст ошибку
которая означает – неоднозначный вызов. Здесь нужно выбирать один из двух вызовов (классов)
Если в метод Request() вписать
то это будет вызов двух методов вместо одного, что тоже не подходит под идеологию паттерна.
Чтобы реализовать возможность одновременной работы со многими адаптированными объектами адаптер для класса нужно заменить на адаптер для объекта (смотрите ниже).
7. Результаты паттерна Adapter для объекта. Примеры
Если паттерн Adapter применяется для объекта (смотрите рисунок 2), то можно получить следующие результаты.
Пример 2. В примере демонстрируется замещение операции в классе, который нужно адаптировать, для паттерна Adapter для объектов.
Чтобы заместить операцию (метод) SpecificRequest() в классе Adaptee выполняются следующие действия:
Адаптер
Адаптер — это структурный паттерн проектирования, который позволяет объектам с несовместимыми интерфейсами работать вместе.
Представьте, что вы делаете приложение для торговли на бирже. Ваше приложение скачивает биржевые котировки из нескольких источников в XML, а затем рисует красивые графики.
В какой-то момент вы решаете улучшить приложение, применив стороннюю библиотеку аналитики. Но вот беда — библиотека поддерживает только формат данных JSON, несовместимый с вашим приложением.
Подключить стороннюю библиотеку не выйдет из-за несовместимых форматов данных.
Вы смогли бы переписать библиотеку, чтобы та поддерживала формат XML. Но, во-первых, это может нарушить работу существующего кода, который уже зависит от библиотеки. А во-вторых, у вас может просто не быть доступа к её исходному коду.
Вы можете создать адаптер. Это объект-переводчик, который трансформирует интерфейс или данные одного объекта в такой вид, чтобы он стал понятен другому объекту.
При этом адаптер оборачивает один из объектов, так что другой объект даже не знает о наличии первого. Например, вы можете обернуть объект, работающий в метрах, адаптером, который бы конвертировал данные в футы.
Адаптеры могут не только переводить данные из одного формата в другой, но и помогать объектам с разными интерфейсами работать сообща. Это работает так:
Иногда возможно создать даже двухсторонний адаптер, который работал бы в обе стороны.
Программа может работать со сторонней библиотекой через адаптер.
Содержимое чемоданов до и после поездки за границу.
Когда вы в первый раз летите за границу, вас может ждать сюрприз при попытке зарядить ноутбук. Стандарты розеток в разных странах отличаются. Ваша европейская зарядка будет бесполезна в США без специального адаптера, позволяющего подключиться к розетке другого типа.
Адаптер объектов
Эта реализация использует агрегацию: объект адаптера «оборачивает», то есть содержит ссылку на служебный объект. Такой подход работает во всех языках программирования.
Клиент — это класс, который содержит существующую бизнес-логику программы.
Клиентский интерфейс описывает протокол, через который клиент может работать с другими классами.
Сервис — это какой-то полезный класс, обычно сторонний. Клиент не может использовать этот класс напрямую, так как сервис имеет непонятный ему интерфейс.
Адаптер — это класс, который может одновременно работать и с клиентом, и с сервисом. Он реализует клиентский интерфейс и содержит ссылку на объект сервиса. Адаптер получает вызовы от клиента через методы клиентского интерфейса, а затем переводит их в вызовы методов обёрнутого объекта в правильном формате.
Работая с адаптером через интерфейс, клиент не привязывается к конкретному классу адаптера. Благодаря этому, вы можете добавлять в программу новые виды адаптеров, независимо от клиентского кода. Это может пригодиться, если интерфейс сервиса вдруг изменится, например, после выхода новой версии сторонней библиотеки.
Адаптер классов
Эта реализация базируется на наследовании: адаптер наследует оба интерфейса одновременно. Такой подход возможен только в языках, поддерживающих множественное наследование, например, C++.
Адаптер классов не нуждается во вложенном объекте, так как он может одновременно наследовать и часть существующего класса, и часть сервиса.
В этом шуточном примере Адаптер преобразует один интерфейс в другой, позволяя совместить квадратные колышки и круглые отверстия.
Пример адаптации квадратных колышков и круглых отверстий.
Адаптер вычисляет наименьший радиус окружности, в которую можно вписать квадратный колышек, и представляет его как круглый колышек с этим радиусом.
Когда вы хотите использовать сторонний класс, но его интерфейс не соответствует остальному коду приложения.
Адаптер позволяет создать объект-прокладку, который будет превращать вызовы приложения в формат, понятный стороннему классу.
Когда вам нужно использовать несколько существующих подклассов, но в них не хватает какой-то общей функциональности, причём расширить суперкласс вы не можете.
Вы могли бы создать ещё один уровень подклассов и добавить в них недостающую функциональность. Но при этом придётся дублировать один и тот же код в обеих ветках подклассов.
Более элегантным решением было бы поместить недостающую функциональность в адаптер и приспособить его для работы с суперклассом. Такой адаптер сможет работать со всеми подклассами иерархии. Это решение будет сильно напоминать паттерн Декоратор.
Убедитесь, что у вас есть два класса с несовместимыми интерфейсами:
Опишите клиентский интерфейс, через который классы приложения смогли бы использовать класс сервиса.
Создайте класс адаптера, реализовав этот интерфейс.
Поместите в адаптер поле, которое будет хранить ссылку на объект сервиса. Обычно это поле заполняют объектом, переданным в конструктор адаптера. В случае простой адаптации этот объект можно передавать через параметры методов адаптера.
Реализуйте все методы клиентского интерфейса в адаптере. Адаптер должен делегировать основную работу сервису.
Приложение должно использовать адаптер только через клиентский интерфейс. Это позволит легко изменять и добавлять адаптеры в будущем.
- Отделяет и скрывает от клиента подробности преобразования различных интерфейсов.
Мост проектируют загодя, чтобы развивать большие части приложения отдельно друг от друга. Адаптер применяется постфактум, чтобы заставить несовместимые классы работать вместе.
Адаптер меняет интерфейс существующего объекта. Декоратор улучшает другой объект без изменения его интерфейса. Причём Декоратор поддерживает рекурсивную вложенность, чего не скажешь об Адаптере.
Адаптер предоставляет классу альтернативный интерфейс. Декоратор предоставляет расширенный интерфейс. Заместитель предоставляет тот же интерфейс.
Фасад задаёт новый интерфейс, тогда как Адаптер повторно использует старый. Адаптер оборачивает только один класс, а Фасад оборачивает целую подсистему. Кроме того, Адаптер позволяет двум существующим интерфейсам работать сообща, вместо того, чтобы задать полностью новый.
Мост, Стратегия и Состояние (а также слегка и Адаптер) имеют схожие структуры классов — все они построены на принципе «композиции», то есть делегирования работы другим объектам. Тем не менее, они отличаются тем, что решают разные проблемы. Помните, что паттерны — это не только рецепт построения кода определённым образом, но и описание проблем, которые привели к данному решению.
Не втыкай в транспорте
Лучше почитай нашу книгу о паттернах проектирования.
Теперь это удобно делать даже во время поездок в общественном транспорте.
Эта статья является частью нашей электронной книги Погружение в Паттерны Проектирования.
Какие задачи решает шаблон проектирования Адаптер
Подробнее о проблеме
Принцип работы паттерна Адаптер
Когда использовать Адаптер
Если нужно использовать сторонний класс, но его интерфейс не совместим с основным приложением. На примере выше видно, как создается объект-прокладка, который оборачивает вызовы в понятный для целевого объекта формат.
Когда у нескольких существующих подклассов должен быть общий функционал. Вместо дополнительных подклассов (их создание приведет к дублированию кода) лучше использовать адаптер.
Преимущества и недостатки
Не путать с Фасадом и Декоратором
Пошаговый алгоритм реализации
Для начала убедись, что есть проблема, которую может решить этот паттерн.
Определи клиентский интерфейс, от имени которого будет использоваться другой класс.
Реализуй класс адаптера на базе интерфейса, определенного на предыдущем шаге.
В классе адаптера сделай поле, в котором хранится ссылка на объект. Эта ссылка передается в конструкторе.
Реализуй в адаптере все методы клиентского интерфейса. Метод может:
Передавать вызов без изменения;
Изменять данные, увеличивать/уменьшать количество вызовов целевого метода, дополнительно расширять состав данных и тд.
В крайнем случае, при несовместимости конкретного метода, выбросить исключение UnsupportedOperationException, которое строго нужно задокументировать.
Если приложение будет использовать адаптер только через клиентский интерфейс (как в примере выше), это позволит безболезненно расширять адаптеры в будущем.
Само собой, паттерн проектирования — это не панацея от всех бед, но с его помощью можно элегантно решить задачу несовместимости объектов с разными интерфейсами. Разработчик, знающий базовые паттерны, — на несколько ступенек выше тех, кто просто умеет писать алгоритмы, ведь они нужны для создания серьезных приложений. Повторно использовать код становится не так сложно, а поддерживать — одно удовольствие. На сегодня все! Но мы скоро продолжим знакомство с разными шаблонами проектирования 🙂
Паттерн проектирования «Адаптер» / «Adapter»
Почитать описание других паттернов.
Интро
Как Вы уже успели заметить, данная секция является не обязательной в моих постах про паттерны. Но я не мог начать писать о конкретном паттерне, не объяснив, зачем они собственно нужны.
Не так давно, когда я был на младших курсах, на мое высказывание «Да мы уже умеем писать программы!», мой коллега сказал — «Максимум, что вы умеет писать — это алгоритмы, но не программы.» Эти его слова я вспоминаю до сих пор с улыбкой на лице. Он был совершенно прав, все чему нас научили за 3 года (я тогда был на третьем курсе) — реализация базовых алгоритмов из Кормена/Кнута. Мы действительно могли писать эти алгоритмы, могли писать функции, процедуры реализующие их. Даже могли написать класс на C++ или Java в котором описать все логику работы с данным классом объектов. Но когда таких классов становилось два или даже три 🙂 начинались проблемы. И при любой попытке написания какой-либо «программы» начиналось изобретение велосипеда (я думаю, что каждый кто читает этот пост, сам изобрел несколько таких велосипедов). Тогда, я начал подозревать, что должно быть что-то, какая теоретическая база, аппарат, механизм, называйте это как хотите, которая в принципе расскажет мне — «как писать программы».
Оказалось, такая база есть — это паттерны проектирования. По большому счету, паттерн — это типичное решение общих проблем проектирования. Паттерны решают ряд основных проблем, связанных с программированием/проектированием. Это — изобретение велосипеда, проблема повторного использования кода, проблема сопровождения кода.
Безусловно, шаблоны проектирования если не решают, то упрощают решение данных проблем. Так, разработчик или проектировщик, зная хотя бы базовый набор паттернов, использует их в замену придумываемым велосипедам. Повторное использование кода становится более прозрачное и оправданное. А сопровождать код, написанный с использованием паттернов как минимум понятно и не затруднительно.
Теперь, мне кажется можно перейти к рассмотрению конкретного паттерна — «Адаптера» («Adapter»).
Проблема
Обеспечить взаимодействие объектов с различными интерфейсами. Адаптировать, а не переписывать существующий код к требуемому интерфейсу.
Описание
Паттерн «Адаптер», на самом деле, является одним из немногих, который программисты применяют на практике, сами того не осознавая. Адаптер можно найти, пожалуй в любой современной программой системе — будь это простое приложение или, например, Java API.
Взглянем более детально на проблему, для понимая того как должен выглядеть Адаптер. Проблема, опять-таки, заключается в повторном использовании кода. Иными словами, есть клиент, который умеет работать с некоторым интерфейсом, назовем его клиентским. Есть класс, который, в принципе, делает то, что нужно клиенту но не реализует клиентский интерфейс. Безусловно, программирование нового класса довольно бессмысленная трата времени и ресурсов. Проще адаптировать уже существующий код к виду, пригодному для использования клиентом. Для этого и существует адаптер. Причем, разделяют два вида адаптеров — Object Adapter (адаптер на уровне объекта) и Class Adapter (адаптер на уровне класса). Мы рассмотри оба, но по порядку.
Практическая задача
Для примера, рассмотрим простую ситуацию. Есть некоторый класс — SequenceGenerator, генерирующий последовательности целых чисел, по определенному закону — это и есть наш клиент. Есть интерфейс — Generator, который использует клиент непосредственно для генерации каждого отдельного элемента последовательности — это наш клиентский интерфейс. К тому-же, есть класс RandomGenerator, который уже умеет генерировать случайные числа. Конечно, SequenceGenerator не может использовать RandomGenerator для генерации элементов, потому что он не соответствует клиентскому интерфейсу. Наша задача — написать адаптер (двумя способами) RandomGenerator к SequenceGenerator.
Диаграммы классов
Object Adapter
Class Adapter
Итак, имея диаграммы классов давайте поговорим об отличиях между адаптером на уровне объекта и адаптером на уровне класса. На самом деле различия видны уже из названия. В первом случае, адаптируемый объект (RandomGenerator) является полем (ссылкой) в классе адаптера (RandomGeneratorAdapter), во втором же он и является адаптером за счет использования механизма наследования. В реальных проектах, рекомендуется использовать Object Adapter, за счет его меньшей связности с адаптируемым объектом.
Реализация
Рассмотрим реализацию поставленной задачи. Object Adapter я реализовывал на С++, Class Adapter на Java.
Object Adapter
class Generator <
public :
virtual int next() = 0;
class SequenceGenerator <
private :
Generator *generator;
protected :
public :
SequenceGenerator(Generator& generator);
int * generate( int length);
>;
int * SequenceGenerator::generate( int length) <
int *ret = new int [length];
class RandomGenerator <
public :
inline int getRandomNumber() < return 4; >; // It`s really random number.
class RandomGeneratorAdapter : public Generator <
private :
RandomGenerator *adaptee;
public :
int main( int argc, char *argv[]) <
RandomGenerator rgenerator;
RandomGeneratorAdapter adapter(rgenerator);
SequenceGenerator sgenerator(adapter);
const int SIZE = 10;
int *seq = sgenerator.generate(SIZE);
Class Adapter
Классическая реализация паттерна Class Adapter подразумевает использование множественного наследования. В Java, можно воспользоваться имплементацией интерфейсов. На мой взгляд, это даже как-то корректнее.
public class SequenceGenerator <
private Generator generator;
public int [] generate( int length) <
int ret[] = new int [length];
for ( int i=0; i return ret;
>
>
public class RandomGenerator <
public int getRandomNumber() <
return 4;
>
>
public class RandomGeneratorAdapter extends RandomGenerator implements Generator <
@Override
public int next() <
return getRandomNumber();
>
// Использование
public class Main <
public static void main( String [] args) <
RandomGeneratorAdapter adapter = new RandomGeneratorAdapter();
SequenceGenerator generator = new SequenceGenerator(adapter);
На этом все. Жду Ваших отзывов в комментариях.
Java. Adapter Pattern in Game Server
Адаптер — это шаблон структурного проектирования, который позволяет объектам с несовместимыми интерфейсами взаимодействовать друг с другом.
Также известен как “Обертка”.
Проблема
Чтобы продемонстрировать этот шаблон, я буду использовать упрощенный пример игровой механики, в которой есть интерфейс IEnemy, но один из врагов отличается от других и не имеет реализации метода атаки. Вместо этого, этот конкретный враг (SpecialEnemy) накладывает заклинания.
Решение
Вы можете создать адаптер. Это специальный объект, который преобразует интерфейс одного объекта, чтобы другой объект мог его понять. Адаптер оборачивает один из объектов, чтобы скрыть сложность преобразования, происходящего за кулисами. Обернутый объект даже не знает об адаптере. Например, вы можете обернуть объект, который работает в метрах и километрах, с помощью адаптера, который преобразует все данные в британские единицы, такие как футы и мили. Адаптеры могут не только преобразовывать данные в различные форматы, но также могут помогать объектам с разными интерфейсами взаимодействовать друг с другом. Вот как это работает:
Адаптер получает интерфейс, совместимый с одним из существующих объектов.
Используя этот интерфейс, существующий объект может безопасно вызывать методы адаптера.
При получении вызова адаптер передает запрос второму объекту, но в формате и порядке, которые ожидает второй объект.
Иногда даже можно создать двусторонний адаптер, который может конвертировать вызовы в обоих направлениях. Вернемся к нашему игровому приложению. Чтобы решить дилемму несовместимых врагов, вы можете создать адаптер EnemyAdapter для каждого класса особенных врагов, с которым ваш код работает напрямую. Затем вы настраиваете свой код для связи с SpecialEnemy только через эти адаптеры. Когда адаптер получает вызов, он переводит входящие атаки в кастование спеллов.
Структура
Существует несколько видов адаптеров
Object adapter
В этой реализации используется принцип композиции объектов: адаптер реализует интерфейс одного объекта и обертывает другой. Его можно реализовать на всех популярных языках программирования.
Class adapter
В этой реализации используется наследование: адаптер наследует интерфейсы от обоих объектов одновременно. Обратите внимание, что этот подход может быть реализован только в языках программирования, поддерживающих множественное наследование, таких как C ++.
Применимость
Используйте класс Adapter, если вы хотите использовать какой-либо существующий класс, но его интерфейс несовместим с остальной частью вашего кода.
Шаблон адаптера позволяет вам создать класс среднего уровня, который служит транслятором между вашим кодом и унаследованным классом, сторонним классом или любым другим классом со странным интерфейсом.
Используйте шаблон, если вы хотите повторно использовать несколько существующих подклассов, в которых отсутствуют некоторые общие функции и нельзя добавить в суперкласс.
Вы можете расширить каждый подкласс и добавить недостающие функции в новые дочерние классы. Однако вам придется продублировать код во всех этих новых классах, что очень плохо «пахнет».
Гораздо более элегантным решением было бы поместить недостающие функции в класс адаптера. Затем вы должны обернуть объекты с недостающими функциями внутри адаптера, динамически получая необходимые функции. Чтобы это работало, целевые классы должны иметь общий интерфейс, а поле адаптера должно следовать за этим интерфейсом. Этот подход очень похож на шаблон Decorator.
Реализация
Добавляем специальных врагов.
Создадим класс SpecialEnemy, использующий метод CastSpell.
Я буду использовать строку возврата, чтобы упростить процесс.
Затем, создадим интерфейс IEnemy для управления поведением врагов, в данном случае это только один метод для атаки
Теперь нам нужно реализовать этот интерфейс в классе EnemyAdapter, который соединит обе части вместе.
Нам нужно добавить ссылку на SpecialEnemy, чтобы иметь доступ к методу CastSpell. Таким образом, мы можем использовать метод атаки без реализации его в SpecialEnemy. Или мы можем создать конструктор и передать туда экземпляр SpecialEnemy.
В качестве вывода мы получаем строку «использование заклинания». Этот пример был слишком упрощен, но он демонстрирует идею, лежащую в основе этого простого шаблона. Надеюсь, он вам понравился и был полезен.