Similar presentations:
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
Фокс Йовович
Рубероид оптом и в розницу
Ищете рубероид? Предлагаем
рубероид по низкой цене
Вам новое сообщение
Иван Иванов прислал вам новое
сообщение
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, адаптируя доступ к
базе данных
programming