Адаптер(adapter)
KarmanovichDevСегодня мы поговорим о таком паттерне проектирования как адаптер.
Из вики: адаптер — структурный шаблон проектирования, предназначенный для организации использования функций объекта, недоступного для модификации, через специально созданный интерфейс. Другими словами — это структурный паттерн проектирования, который позволяет объектам с несовместимыми интерфейсами работать вместе.
Для начала рассмотрим несколько примеров из реальной жизни, чтобы помочь нам понять основную суть:
- Один из самых распространённых примеров, это конечно же адаптер для розетки

- Или картридер для вашего ПК или музыкального центра

- Различные адаптеры для конвертации потока данных при работе с ПК

Обратите внимание, что в примерах ни разу не пришлось изменять ни то, к чему мы адаптируем, ни то, что мы адаптируем. Для использования паттерна адаптер подразумевается, что оба объекта с несовместимыми интерфейсами уже существуют и нам нужно модифицировать ни один из них.
А теперь давайте посмотрим на примеры, когда паттерн адаптер может помочь нам как программистам:
- Представьте, что у вас есть приложение, которое возвращает усреднённый курс по ценным бумагам, полученный из множества доступных бирж. До какой-то поры всё было хорошо, так как все популярные биржи придерживались очень похожего интерфейса общения по протоколу REST. Вы не успели заметить, как ваш код стал завязываться на эти детали реализации, и большая часть вашего приложения стала ожидать в своих интерфейсах(порты) взаимодействия такой формат(REST). В один прекрасный(нет!) день к вам пришёл менеджер и сказал, что, чтобы улучшить свои позиции на рынке агрегаторов, вы должны подключить ещё две конкретные биржи. Одна была довольно старая и использовала ранее неиспользуемый вами протокол обмена данными SOAP, а вторая биржа использовала JSONRPC, что так-же было для вас новым видом взаимодействия. В этот момент вы осознали, что текущий код не готов работать ни с одним из новых для вас форматов в данный момент, и начали судорожно думать, как сделать систему отвязанной от способа передачи данных, либо как сделать костыль, который бы мог дать вам принимать решения не на таком уровне стресса. Представили? Так вот, таким "костылём" может стать паттерн адаптер.

- Также может быть, вы встречали в бизнес-коде if(ы), которые завязаны на детали реализации инфраструктуры?😏

Конечно, по-хорошему нужно выделить общий интерфейс и "загнать" под него оба эти класса, но что если в данный момент это сделать достаточно сложно? Что, если сейчас на это эти классы завязана большАя часть вашего приложения? В таком случае вы не сможете сделать это быстро без существенных правок в коде. Учитывая, что масштабный рефакторинг "за раз" в 99% случаев невозможен в силу нехватки времени, вам приходится делать очередное TODO и надеяться, что когда-нибудь вы сможете это исправить.
С другой стороны, вы могли бы достаточно малыми силами начать этот рефакторинг прямо сейчас в рамках своей задачи! Но как? Даже в самой плохой ситуации, когда вы не можете по каким-либоб причинам отвязаться от деталей реализации и выделить интерфейс, начните с малого и сделайте адаптер одних деталей реализации под другие, которые с большей вероятностью будут являться основой для интерфейса в дальнейшем:

Если у вас есть возможность сделать больший шаг, то вы можете выделить интерфейс:

UML схема:

Вы можете задаться вопросом, почему RedisUserRepository так же не вынесен под интерфейс BaseUserRepository? Вспомним, что я говорил выше: ваш текущий код может быть сильно завязан на уже имеющиеся детали реализации, что мешает сделать все изменения, которые вы запланировали за один шаг.
Также, делая адаптер в данной ситуации вам уже, не нужно проверять все инварианты и сложность RedisUserRepository, они уже протестированы ранее. Вам нужно лишь добавить пару тестов на проверку работы адаптера.
Всё вышесказанное не отменяет того, что если полная доработка кода не занимает много сил и времени, её можно сделать и за один шаг. Но знание того, что можно сделать шаг поменьше с помощью адаптера, а не оставлять очередное TODO может помочь вам начать приводить ваш код в порядок сейчас а не в будущем.
Два простых примера, когда мы можем использовать адаптер и где при всём желании не можем изменить адаптируемый объект:
- InMemoryRepository/FakeRepository

- Очередь/стек на основе списка

По идее большинство классов, которые мы пишем для взаимодействия с инфраструктурой(сторонними библиотеками) без большого кол-ва вызовов и без логики являются адаптерами. Если появляется большое кол-во вызовов или сложная логика, это скорее уже паттерн фасад, но нужно смотреть конкретные примеры, что бы сказать точно.