Паттерн проектирования «Адаптер»
Адаптеры вокруг нас
Ты помнишь, как все начиналось?
В новой версии библиотеки изменился интерфейс
Решение – переписать систему!
Альтернативное решение – использовать адаптер
Утки и Индюшки
Адаптер, превращающий индюшек в уток
Тестируем адаптер
Адаптер в сказке «Три поросёнка»
Работа паттерна адаптер
Паттерн «Адаптер»
Структура паттерна «Адаптер»
Адаптер объектов
Адаптер объектов
Адаптер классов
Адаптер классов
Применимость
Объем работы
Препятствия использованию адаптеров
Сравнение Адаптера и Декоратора
Сравнение Адаптера и Декоратора
Адаптер в функциональном стиле
Адаптер в функциональном стиле
Пример: адаптер в TypeScript
Адаптер с преобразованием результата
Пример – Адаптер в C++
Функциональные адаптеры в реальной практике
Идея функционального адаптера
Примеры использования паттерна «Адаптер»
Примеры использования
Пример – элемент управления «список»
Описание задачи
Варианты решения
Передавать массив IListItemView
Анализ решения
Пример, подключить клиента к одному из Text to Speech сервисов
Адаптер
Работа с несколькими TTS-сервисами
Спасибо за внимание
3.75M
Category: programmingprogramming

adapter

1. Паттерн проектирования «Адаптер»

2. Адаптеры вокруг нас

3. Ты помнишь, как все начиналось?

Существующая
система
Внешний
класс

4. В новой версии библиотеки изменился интерфейс

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

5. Решение – переписать систему!

Существующая
система
Внешний
класс
Может быть слишком
трудоемкой задачей
А завтра внешний класс
снова изменится и
придется снова
переписывать?

6. Альтернативное решение – использовать адаптер

Существующая
система
Адаптер реализует
интерфейс, на который
рассчитаны классы системы
Адаптер
Внешний
класс
Адаптер взаимодействует с
внешними классами через их
интерфейс для выполнения
запросов

7. Утки и Индюшки

struct IDuck
{
virtual void Quack() = 0;
virtual void Fly() = 0;
virtual ~IDuck() = default;
};
struct ITurkey
{
virtual void Gobble() = 0;
virtual void Fly() = 0;
virtual ~ITurkey() = default;
};
class MallardDuck : public IDuck
{
public:
Интерфейсы
void Quack() override
несколько
{
различаются
cout << "Quack\n";
}
void Fly() override
{
cout << "I'm flying\n";
}
};
class WildTurkey : public ITurkey
{
public:
void Gobble() override
{
cout << "Gobble gobble\n";
}
void Fly() override
{
cout << "I'm flying a short distance\n";
}
};
Код зависит от интерфейса, а не от
void TestDuck(IDuck & duck)
конкретных классов. Это хорошо.
{
Клиентский код использует интерфейс
duck.Quack();
IDuck. Как заставить его работать еще и
duck.Fly();
с ITurkey?
}

8. Адаптер, превращающий индюшек в уток

class TurkeyToDuckAdapter : public IDuck
{
public:
TurkeyToDuckAdapter(ITurkey & turkey)
:m_turkey(turkey)
{}
void Quack() override
{
m_turkey.Gobble();
}
Адаптер должен реализовывать тот
интерфейс, на который рассчитан
клиент
Получаем ссылку на адаптируемый объект
Можно использовать умный указатель
Обычно адаптер получает ссылку в
конструкторе
Вместо кряканья индюшки будут
курлыкать
void Fly() override
{
for (int i = 0; i < 5; ++i)
{
m_turkey.Fly();
}
}
private:
ITurkey & m_turkey;
};
Индюшки плохо летают. Поэтому будут летать
пять раз.

9. Тестируем адаптер

int main()
{
MallardDuck mallardDuck;
TestDuck(mallardDuck);
В работе с утками нет ничего
особенного
WildTurkey wildTurkey;
TurkeyToDuckAdapter turkeyAdapter(wildTurkey);
TestDuck(turkeyAdapter);
return 0;
}
Чтобы индюшка выглядела
как утка, заворачиваем
ее в TurkeyAdapter
TestDuck не
подозревает, что
работает не с уткой

10. Адаптер в сказке «Три поросёнка»

11. Работа паттерна адаптер

Адаптируемый объект
Клиент
Адаптер
Реализация клиента
использует целевой
интерфейс
Адаптер реализует целевой
интерфейс и хранит ссылку на
экземпляр адаптируемого объекта
Клиент обращается с запросом
Адаптер преобразует запрос в
к адаптеру, вызывая его
один или несколько вызовов к
метод через целевой
адаптируемому объекту (в
интерфейс
интерфейсе последнего).
Клиент не знает про наличие
Интерфейс
адаптируемого объекта
Клиент получает
результаты вызова, не
подозревая о
преобразованиях,
выполненных адаптером

12. Паттерн «Адаптер»

• Преобразует интерфейс класса к другому
интерфейсу, который используют клиенты
• Обеспечивает совместную работу классов,
невозможную в обычных условиях из-за
несовместимости интерфейсов
• Адаптер защищает код клиента от изменений в
используемом интерфейсе

13. Структура паттерна «Адаптер»

Интерфейс, нужный клиенту
Клиент видит только интерфейс
Target
Адаптер
реализует
интерфейс
Target
Адаптируемый объект
Адаптер связывается с адаптируемым
объектом посредством композиции или
Все запросы делегируются
адаптируемому классу

14. Адаптер объектов

• Способ реализовать паттерн через композицию или
агрегацию.
• Адаптер реализует целевой интерфейс, содержит ссылку
на адаптируемый объект и делегирует вызовы
• Плюсы
• Один адаптер позволяет с любым потомком
адаптируемого класса
• Можно адаптировать уже готовые объекты
• Легко расширять поведение
• Минусы
• Трудно переопределять операции подкласса Adaptee
• Нужно создавать подкласс Adaptee и заставить Adapter
ссылаться на него, а не на сам Adaptee

15. Адаптер объектов

Адаптер может работать с любыми наследниками адаптируемого
интерфейса

16. Адаптер классов

Вместо композиции Adapter
наследуется от Target и
Adaptee

17. Адаптер классов

• Адаптер наследуется от Target и Adaptee.
• Плюсы
• Адаптер может переопределить поведение адаптируемого
класса
• Меньше объектов – всё объединено в одном
• Минусы
• Подходит только для конкретного класса, а не для
всей иерархии
• В C++ можно уменьшить дублирование кода, используя шаблоны
• Требуется поддержка множественного наследования

18.

class Target {
public:
virtual void Request() = 0;
virtual ~Target() = default;
};
class Adaptee {
public:
void SpecificRequest() {
Operation1();
Operation2();
Operation3();
}
private:
virtual void Operation1() = 0;
void Operation2() {
std::cout << "Adaptee::Operation2()\n";
}
virtual void Operation3() {
std::cout << "Adaptee::Operation2()\n";
}
};
class ConcreteAdaptee : public Adaptee
{
private:
void Operation1() override {
cout << "ConcreteAdaptee::Operation1\n";
}
void Operation3() override {
cout << "ConcreteAdaptee::Operation2()\n";
}
};

19.

template <typename AdapteeType = Adaptee>
// AdapteeType должен быть наследником Adaptee
requires(std::derived_from<AdapteeType, Adaptee>)
class AdapteeToTargetAdapter
: public Target
, private AdapteeType
{
public:
void Request() override {
// this-> нужен, чтобы сказать компилятору,
// что SpecificRequest — это метод текущего объекта, унаследованный от AdapteeType
this->SpecificRequest();
}
private:
void Operation1() override { // Переопределяем операцию из AdapteeType
cout << "AdapteeToTargetAdapter::Operation1\n";
}
};
void Test() {
AdapteeToTargetAdapter<> adapter1;
void TestClient(Target& target) {
TestClient(adapter1);
target.Request();
}
AdapteeToTargetAdapter<ConcreteAdaptee> adapter2;
TestClient(adapter2);
}

20. Применимость

• Нужно заставить старый код работать с новым
интерфейсом, не переписывания ни код
библиотеки, ни код клиента
• Интерфейсы несовместимы, но по смыслу делают
одно и то же
• Клиенту не важно, как происходит именно вызов

21. Объем работы

• Сложность реализации адаптера пропорциональна
размеру целевого интерфейса
• Альтернатива – переписывать код клиентов

22. Препятствия использованию адаптеров

• Зависимость клиента от конкретных классов
• Адаптер легче внедрить в код, зависящий от
интерфейсов
• Замаскированные зависимости от конкретных
классов
• Код внешне зависит от интерфейсов, но внутри
использует приведение типа вниз по иерархии

23. Сравнение Адаптера и Декоратора

Декоратор
Адаптер
• Не изменяет интерфейс,
но добавляет новое
поведение
• Код клиентов не
изменяется при
добавлении поведения в
систему
• Если нужно добавить
фичу
• Изменяет интерфейс,
чтобы старый код мог
работать с новым
объектом
• Код клиентов не
изменяется при смене
интерфейса
адаптируемого класса
• Если нужно «перевести
общение» с объектом на
другой язык

24. Сравнение Адаптера и Декоратора

25. Адаптер в функциональном стиле

26. Адаптер в функциональном стиле

• В ОО-парадигме – класс, реализующий один
интерфейс и содержащий ссылку на другой объект
• Функциональный адаптер — это функция, которая
преобразует одну функцию в другую, с другой
сигнатурой или контрактом.

27. Пример: адаптер в TypeScript

// Клиент ожидает функцию вида:
type Handler = (data: string) => void;
// А у нас есть старая функция с другим интерфейсом:
type LegacyHandler = (user: { name: string }) => void;
const legacyHandler: LegacyHandler = (u) => {
console.log("Hello,", u.name);
};
// Функциональный адаптер
const adaptHandler = (f: LegacyHandler): Handler => {
return (s) => f({ name: s });
};
// Использование
const handler: Handler = adaptHandler(legacyHandler);
handler("Alice"); // Hello, Alice

28. Адаптер с преобразованием результата

// Старое API: синхронная функция
type ApiV1 = (id: number) => string;
// Новое API: возвращает Promise
type ApiV2 = (id: number) => Promise<string>;
// Исходная синхронная функция
const apiV1: ApiV1 = (id) => `User #${id}`;
// Адаптер: ApiV1 → ApiV2
const adaptApi = (f: ApiV1): ApiV2 => {
return async (id) => f(id);
};
// Использование
(async () => {
const api = adaptApi(apiV1);
console.log(await api(42)); // User #42
})();

29. Пример – Адаптер в C++

// Исходная функция
using LegacyHandler = std::function<void(const std::string&, int)>;
// Новый интерфейс
using NewHandler = std::function<void(int)>;
// Функциональный адаптер
NewHandler adaptHandler(LegacyHandler legacy, std::string name) {
return [=](int id) {
legacy(name, id);
};
}
int main() {
LegacyHandler oldFn = [](const std::string& name, int id) {
std::cout << "Hello " << name << ", id = " << id << '\n';
};
auto newFn = adaptHandler(oldFn, "Alice");
newFn(42);
}

30. Функциональные адаптеры в реальной практике

• callback → promise / future
• sync → async
• изменение формата данных
• частичное применение аргументов
• перестановка аргументов
• адаптация к внешним API

31. Идея функционального адаптера

• Функциональный адаптер — это способ выразить ту
же идею, но средствами функционального
программирования
• Суть — инкапсулировать различия между
интерфейсами
• Адаптер делает взаимодействие возможным без
изменения исходного кода
• Паттерн привязан не к парадигме, а к самой идее
совместимости интерфейсов.

32. Примеры использования паттерна «Адаптер»

33. Примеры использования

• Адаптеры, инкапсулирующие доступ к различным
базам данных
• Адаптеры к графическим API
• Адаптеры для доступа к данным в
стандартизованных элементах управления

34. Пример – элемент управления «список»

Содержание
1. Введение
2. Основная часть
3. Заключение
4. Список литературы
Входящие
Elena Ivanova
Обновление CRM
Всем привет, сегодня состоялось
обновление crm
Фокс Йовович
Рубероид оптом и в розницу
Ищете рубероид? Предлагаем
рубероид по низкой цене
Facebook
Вам новое сообщение
Иван Иванов прислал вам новое
сообщение

35. Описание задачи

• Разработать компонент «Список», способный
отображать данные произвольного рода в виде
списка
• Текст
• Список пользователей
• Список писем
• Клиенты должны иметь возможность гибко
настраивать внешний вид элементов
• Заранее неизвестно, как именно

36. Варианты решения

• Разработать N классов списков
• Дублирование кода
• Сделать мега-класс списка, позволяющим
настраивать внешний вид элементов
• Как быть с нестандартным внешним видом?
• Список отображает массив визуальных элементов,
передаваемых извне
• Подходит лишь для небольших списков
• Передавать списку адаптер для динамического
создания элементов списка

37. Передавать массив IListItemView

Покритикуйте его

38.

Целевой интерфейс.
Предоставляет доступ к
элементам списка
Список использует
интерфейс
IListViewDataSource для
получения элементов
Интерфейс, который должны
реализовывать все элементы
списка
<<Interface>>
<<Interface>>
IListViewDataSource
IListItemView
+GetItemCount()
+CreateItemView(index):IListItemView
dataSource
ListView
+SetDataSource(dataSource)
+ReloadData()
MessageListItemView
Адаптирует доступ
списка к базе
пользователей
TextListItemView
UserListViewDataSource
UserListItemView
+GetItemCount()
+CreateItemView(index):IListItemView
PictureListItemView
Конкретные реализации элементов
списка
UserDb
+GetNumberOfUsers()
+GetUserInfo(index)
UserInfo
1
Адаптируемый интерфейс
*
+name
+title
+photoURL

39. Анализ решения

• ListView использует интерфейс
IListViewDataSource
• Узнать количество элементов в списке
• Создание визуального элемента списка по его индексу,
когда в нем возникнет необходимость
• Адаптер UserListViewDataSource создает
экземпляры UserListItemView, адаптируя доступ к
базе данных

40. Пример, подключить клиента к одному из Text to Speech сервисов

41. Адаптер

42. Работа с несколькими TTS-сервисами

43. Спасибо за внимание

English     Русский Rules