JavaRush /Курсы /Java Collections /Паттерны: Adapter, Proxy, Bridge

Паттерны: Adapter, Proxy, Bridge

Java Collections
7 уровень , 2 лекция
Открыта

— Привет, друг!

— Привет, Билаабо!

— У нас еще осталось немного времени, поэтому я расскажу тебе про еще три паттерна.

— Еще три, а сколько их всего?

— Ну, сейчас есть несколько десятков популярных паттернов, но количество «удачных решений» не ограничено.

— Ясно. И что, мне придется учить несколько десятков паттернов?

— Пока у тебя нет опыта реального программирования, они дадут тебе не очень много.

Ты лучше поднаберись опыта, а потом, через годик, вернись к этой теме и попробуй разобраться в них более основательно. Хотя бы пару десятков самых популярных.

Грех не пользоваться чужим опытом и самому что-то изобретать в очередной 110-й раз.

— Согласен.

— Тогда начнем.

Паттерн Adapter(Wrapper) – Адаптер (Обертка)

Паттерны: Adapter, Proxy, Bridge - 1

Представь, что ты приехал в Китай, а там другой стандарт розеток. Отверстия не круглые, а плоские. Тогда тебе понадобится переходник, или другими словами – адаптер.

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

Вот как это может выглядеть:

Пример
interface Time
{
 int getSeconds();
 int getMinutes();
 int getHours();
}

interface TotalTime
{
 int getTotalSeconds();
}

Допустим, у нас есть два интерфейса – Time и TotalTime.

Интерфейс Time позволяет узнать текущее время с помощью методов getSeconds(), getMinutes() и getHours().

Интерфейс TotalTime позволяет получить количество секунд, которое прошло от полночи до текущего момента.

Что делать, если у нас есть объект типа TotalTime, а нужен Time и наоборот?

Для этого мы можем написать классы-адаптеры. Пример:

Пример
 class TotalTimeAdapter implements Time
{
 private TotalTime totalTime;
 public TotalTimeAdapter(TotalTime totalTime)
 {
  this.totalTime = totalTime;
 }

 public int getSeconds() 
 {
  return totalTime.getTotalSeconds() % 60; //секунды
 }

 public int getMinutes()
 {
  return (totalTime.getTotalSeconds() % (60*60)) / 60; //минуты
 }

 public int getHours()
 {
  return totalTime.getTotalSeconds() / (60*60); //часы
 }
}
 
Как пользоваться
TotalTime totalTime = ... ; // программа получает объект, который реализует интерфейс TotalTime
Time time = new TotalTimeAdapter(totalTime);
System.out.println(time.getHours()+":"+time.getMinutes()+":"+time.getSeconds());

И адаптер в другую сторону:

Пример
class TimeAdapter implements TotalTime
{
 private Time time;
 public TimeAdapter(Time time)
 {
  this.time = time;
 }

 public int getTotalSeconds()
 {
  return time.getHours()*60*60+time.getMinutes()*60 + time.getSeconds(); 
 }
}
Как пользоваться
Time time = ... ; // программа получает объект, который реализует интерфейс Time
TotalTime totalTime = new TimeAdapter(time);
System.out.println(totalTime.getTotalSeconds());

— Ага. Мне нравится. А примеры есть?

— Конечно, например, InputStreamReader – это классический адаптер. Преобразовывает тип InputStream к типу Reader.

Иногда этот паттерн еще называют обертка, потому что новый класс как бы «оборачивает» собой другой объект.

Другие интересные вещи почитать можно тут.

Паттерн Proxy — Заместитель

Паттерн прокси чем-то похож на паттерн "обертка". Но его задача – не преобразовывать интерфейсы, а контролировать доступ к оригинальному объекту, сохраненному внутри прокси-класса. При этом и оригинальный класс и прокси обычно имеют один и тот же интерфейс, что облегчает подмену объекта оригинального класса, на объект прокси.

Пример:

Интерфейс реального класса
interface Bank
{
 public void setUserMoney(User user, double money);
 public int getUserMoney(User user);
}
Реализация оригинального класса
class CitiBank implements Bank
{
 public void setUserMoney(User user, double money)
 {
  UserDAO.updateMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  return UserDAO.getMoney(user);
 }
}
Реализация прокси-класса
class BankSecurityProxy implements Bank
{
 private Bank bank;
 public BankSecurityProxy(Bank bank)
 {
  this.bank = bank;
 }
 public void setUserMoney(User user, double money)
 {
  if (!SecurityManager.authorize(user, BankAccounts.Manager)) throw new SecurityException("User can’t change money value");

  bank.setUserMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  if (!SecurityManager.authorize(user, BankAccounts.Manager)) throw new SecurityException("User can’t get money value");

  return bank.getUserMoney(user);
 }
}

В примере выше мы описали интерфейс банка – Bank, и одну его реализацию – CitiBank.

Этот интерфейс позволяет получить или изменить количество денег на счету пользователя.

А потом мы создали BankSecurityProxy, который тоже реализует интерфейс Bank и хранит в себе ссылку на другой интерфейс Bank. Методы этого класса проверяют: является ли данный пользователь владельцем счета либо менеджером банка, и если нет – то кидает исключение безопасности – SecurityException.

Вот как это работает на деле:

Код без проверки безопасности:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank.setUserMoney(user, 1000000);
Код с включённой проверкой безопасности:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank = new BankSecurityProxy(bank);
bank.setUserMoney(user, 1000000);

В первом примере мы создаем объект банк и вызываем у него метод setUserMoney.

Во втором примере мы оборачиваем оригинальный объект банк в объект BankSecurityProxy. Интерфейс у них один, так что дальнейший код как работал, так и работает. Но теперь при каждом вызове метода будет происходить проверка безопасности.

— Круто!

— Ага. Таких прокси может быть много. Например, можно добавить еще один прокси, который будет проверять – не слишком ли большая сумма. Может менеджер банка решил положить себе на счет кучу денег и сбежать с ними на Кубу.

Более того. Создание всех этих цепочек объектов можно поместить в класс BankFactory и подключать/отключать нужные из них.

По похожему принципу работает BufferedReader. Это Reader, но который делает еще дополнительную работу.

Такой подход позволяет «собирать» из «кусочков» объект нужной тебе функциональности.

Чуть не забыл. Прокси используются гораздо шире, чем я только что тебе показал. Об остальных типах использования ты можешь почитать здесь.

Паттерн Bridge – Мост

Паттерны: Adapter, Proxy, Bridge - 2

Иногда, в процессе работы программы, надо сильно поменять функциональность объекта. Например, был у тебя в игре персонаж осел, а потом маг превратил его в дракона. У дракона совсем другое поведение и свойства, но(!) это – тот же самый объект.

— А нельзя просто создать новый объект и все?

— Не всегда. Допустим, твой осел был в друзьях у кучи персонажей, или например, на нем были наложены некоторые заклинания, или он участвовал в каких-то квестах. Т.е. этот объект уже может быть задействован в куче мест и привязан к куче других объектов. Так что просто создать новый другой объект в этом случае – не вариант.

— И что же делать?

— Одним из наиболее удачных решений есть паттерн Мост.

Этот паттерн предлагает разделить объект на два объекта. На «объект интерфейса» и «объект реализации».

— А в чем отличие от интерфейса и класса, который его реализует?

— В случае с интерфейсом и классом в результате будет создан один объект, а тут — два. Смотри пример:

Пример
class User
{
 private UserImpl realUser;

 public User(UserImpl impl)
 {
  realUser = impl;
 }

 public void run() //бежать
 {
  realUser.run();
 }

 public void fly() //лететь
 {
  realUser.fly();
 }
}

class UserImpl
{
 public void run() 
 {
 }

 public void fly()
 {
 }
}

А потом можно объявить несколько классов наследников от UserImpl, например UserDonkey(осел) и UserDragon(дракон).

— Все равно не очень понял, как это будет работать.

— Ну, примерно так:

Пример
class User
{
 private UserImpl realUser;

 public User(UserImpl impl)
 {
  realUser = impl;
 }

 public void transformToDonkey()
 {
  realUser = new UserDonkeyImpl();
 }

 public void transformToDragon()
 {
  realUser = new UserDragonImpl();
 }
}
Как это работает
User user = new User(new UserDonkey()); //внутри мы – осел
user.transformToDragon(); //теперь внутри мы – дракон

— Чем-то напоминает прокси.

— Да, только в прокси главный объект мог храниться где-то отдельно, а код работал с прокси-объектами вместо него. Здесь же предлагается, что все работают именно с главным объектом, а меняются его внутренние части.

— Ага. Спасибо. А дашь ссылку почитать еще про это?

— Конечно, друг Амиго. Держи: Паттерн Bridge

Комментарии (53)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
19 апреля 2025
Последовательность для изучения такая: ООП -> SOLID -> GRASP -> GOF -> MAPL
Ra Уровень 5 Student
27 июля 2023
Интересно, есть ли где-то дополнительные задачи на паттерны?
Евгений Т. Уровень 39
30 марта 2023
2023 год все ссылки указанные в этом и предыдущем уроках тухлые.
Нейросеть Уровень 41
22 августа 2023
А я уж было подумал это у меня одного так
8 ноября 2024
Привет из 2024-го
Kurama Уровень 50
27 марта 2023
Короче, как обычно... Глазами пробежался, а потом сюда за нормальным объяснением
Alexey Maleev Уровень 30
15 января 2023
2023 год - ссылка на паттерн Bridge протухшая.
pichitap Уровень 51
26 февраля 2023
Вейдер дело говорит
Алексей Уровень 41
21 ноября 2022
Оберткой называют декоратор, а не адаптер
Kurama Уровень 50
27 марта 2023
Оберткой называют и то, и то Согласен, странно
Макс Дудин Уровень 41
28 июня 2022
"прозрачное" объяснение паттерна "Заместитель" по ссылке в википедии.... "Создать суррогат реального объекта. «Заместитель» хранит ссылку, которая позволяет заместителю обратиться к реальному субъекту (объект класса «Заместитель» может обращаться к объекту класса «Субъект», если интерфейсы «Реального Субъекта» и «Субъекта» одинаковы). Поскольку интерфейс «Реального Субъекта» идентичен интерфейсу «Субъекта», так, что «Заместителя» можно подставить вместо «Реального Субъекта», контролирует доступ к «Реальному Субъекту», может отвечать за создание или удаление «Реального Субъекта». «Субъект» определяет общий для «Реального Субъекта» и «Заместителя» интерфейс так, что «Заместитель» может быть использован везде, где ожидается «Реальный Субъект». При необходимости запросы могут быть переадресованы «Заместителем» «Реальному Субъекту»." лично я теряю нить прочитав 0,75 это текста ... какой-то сон пронесон.. https://www.youtube.com/watch?v=2sJFWlg7Tj0&t=18s
Igor Petrashevsky Уровень 47
26 августа 2022
мы проходили Proxy раньше. суть была в том, что если оригинальный объект генерировал исключение и падал, мы catch'eм подставляли прокси-костыль, который был "тоже самое", только с данными-затычками, лишь бы дальше программа работала "как ни в чем не бывало", Это был один из простых вариантов применения. Все они - узаконенные костыли под какую-либо задачу, как панели в панельных домах. Вот окно. Вот окно с балконной дверью, вот козырек, вот - лестница
Макс Дудин Уровень 41
28 августа 2022
да это всё понятно... и с самим proxy особо вопросов нет, просто объяснение прикольное...
Жора Нет Уровень 39
6 мая 2022
Бляха, как это все запомнить??? Вроде бы понятно, но многие из них(паттернов) настолько похожи, что через пять минут после прочтения уже забыл как реализуется тот или иной шаблон😞
Igor Petrashevsky Уровень 47
26 августа 2022
ну да, это сорта костылей, по сути, или велосипедов. когда выяснится, что сам велосипедов наизобретал, а можно было чуть поправить и взять готовые - жить станет легче.
LuneFox Уровень 41 Expert
24 февраля 2022
Я ведь не один представлял осла и дракониху из Шрека?
Anna Avilova Уровень 51
26 апреля 2022
не один )
26 сентября 2021
Ox my made .