JavaRush /จาวาบล็อก /Random-TH /Какие задачи решает шаблон проектирования Адаптер

Какие задачи решает шаблон проектирования Адаптер

เผยแพร่ในกลุ่ม
Часто разработку ПО усложняет несовместимость компонентов, работающих друг с другом. Например, если нужно интегрировать новую библиотеку со старой платформой, написанной еще на ранних versionх Java, можно столкнуться с несовместимостью an objectов, а точнее — интерфейсов. What делать в таком случае? Переписать code? Но ведь это невозможно: анализ системы займет много времени or же будет нарушена внутренняя логика работы.Какие задачи решает шаблон проектирования Адаптер - 1Для решения этой проблемы придумали паттерн Адаптер, который помогает an objectм с несовместимыми интерфейсами работать вместе. Давай посмотрим, How его использовать!

Подробнее о проблеме

Для начала сымитируем поведение старой системы. Предположим, она генерирует причины опоздания на работу or учебу. Для этого у есть интерфейс Excuse, который содержит методы generateExcuse(), likeExcuse() и dislikeExcuse().

public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
Этот интерфейс реализует класс WorkExcuse:

public class WorkExcuse implements Excuse {
   private String[] reasonOptions = {"по невероятному стечению обстоятельств у нас в доме закончилась горячая вода и я ждал, пока солнечный свет, сконцентрированный через лупу, нагреет кружку воды, чтобы я мог умыться.",
   "искусственный интеллект в моем будильнике подвел меня и разбудил на час раньше обычного. Поскольку сейчас зима, я думал что еще ночь и уснул. Дальше все How в тумане.",
   "предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице."};
   private String[] sorryOptions = {"Это, конечно, не повторится, мне очень жаль.", "Прошу меня извинить за непрофессиональное поведение.", "Нет оправдания моему поступку. Я недостоин этой должности."};

   @Override
   public String generateExcuse() { // Случайно выбираем отговорку из массива
       String result = "Я сегодня опоздал, потому что " + reasonOptions[(int) Math.round(Math.random() + 1)] + "\n" +
               sorryOptions[(int) Math.round(Math.random() + 1)];
       return result;
   }

   @Override
   public void likeExcuse(String excuse) {
       // Дублируем элемент в массиве, чтобы шанс его выпадения был выше
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Удаляем элемент из массива
   }
}
Протестируем пример:

Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Вывод:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице. 
Прошу меня извинить за непрофессиональное поведение.
Теперь представим, что ты запустил сервис, собрал статистику и заметил, что большинство пользователей сервиса — студенты вузов. Whatбы улучшить его под нужды этой группы, ты заказал у другого разработчика систему генерации отговорок специально для нее. Команда разработчика провела исследования, составила рейтинги, подключила искусственный интеллект, добавила интеграцию с пробками на дорогах, погодой и так далее. Теперь у тебя есть библиотека генерации отговорок для студентов, однако интерфейс взаимодействия с ней другой — StudentExcuse:

public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
У интерфейса есть два метода: generateExcuse, который генерирует отговорку, и dislikeExcuse, который блокирует отговорку, чтобы она не появлялась в дальнейшем. Библиотека стороннего разработчика закрыта для редактирования — ты не можешь изменять его исходный code. В итоге в твоей системе есть два класса, реализующие интерфейс Excuse, и библиотека с классом SuperStudentExcuse, который реализует интерфейс StudentExcuse:

public class SuperStudentExcuse implements StudentExcuse {
   @Override
   public String generateExcuse() {
       // Логика нового функционала
       return "Невероятная отговорка, адаптированная под текущее состояние погоды, пробки or сбои в расписании общественного транспорта.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Добавляет причину в черный список
   }
}
Изменить code нельзя. Текущая схема будет выглядеть так: Какие задачи решает шаблон проектирования Адаптер - 2Эта version системы работает только с интерфейсом Excuse. Переписывать code нельзя: в большом приложении такие правки могут затянуться or нарушить логику applications. Можно предложить внедрение основного интерфейса и увеличение иерархии: Какие задачи решает шаблон проектирования Адаптер - 3Для этого нужно переименовать интерфейс Excuse. Но дополнительная иерархия нежелательна в серьезных applicationsх: внедрение общего корневого element нарушает архитектуру. Следует реализовать промежуточный класс, который позволит использовать новый и старый функционал с минимальными потерями. Словом, тебе нужен адаптер.

Принцип работы паттерна Адаптер

Адаптер — это промежуточный an object, который делает вызовы методов одного an object понятными другому. Реализуем адаптер для нашего примера и назовем его Middleware. Наш адаптер должен реализовывать интерфейс, совместимый с одним из an objectов. Пусть это будет Excuse. Благодаря этому Middleware может вызывать методы первого an object. Middleware получает вызовы и передает их второму an objectу в совместимом формате. Так выглядит реализация метода Middleware с методами generateExcuse и dislikeExcuse:

public class Middleware implements Excuse { // 1. Middleware становится совместимым с an objectом WorkExcuse через интерфейс Excuse

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) { // 2. Получаем ссылку на адаптируемый an object
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse(); // 3. Адаптер реализовывает метод интерфейса
   }

    @Override
    public void dislikeExcuse(String excuse) {
        // Метод предварительно помещает отговорку в черный список БД,
        // Затем передает ее в метод dislikeExcuse an object superStudentExcuse.
    }
   // Метод likeExcuse появятся позже
}
Тестирование (в клиентском codeе):

public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // Создаются an objectы классов,
       StudentExcuse newExcuse = new SuperStudentExcuse(); // Которые должны быть совмещены.
       System.out.println("Обычная причина для работника:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Оборачиваем новый функционал в an object-адаптер
       System.out.println("Использование нового функционала с помощью адаптера:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // Адаптер вызывает адаптированный метод
   }
}
Вывод:
Обычная причина для работника:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице.
Нет оправдания моему поступку. Я недостоин этой должности. Использование нового функционала с помощью адаптера
Невероятная отговорка, адаптированная под текущее состояние погоды, пробки or сбои в расписании общественного транспорта. В методе generateExcuse выполнена простая передача вызова другому an objectу, без дополнительных преобразований. Метод dislikeExcuse потребовал предварительного помещения отговорки в черный список базы данных. Дополнительная промежуточная обработка данных — причина, по которой любят паттерн Адаптер. А How быть с методом likeExcuse, который есть в интерфейсе Excuse, но нет в StudentExcuse? Эта операция не поддерживается в новом функционале. Для такого случая придумали исключение UnsupportedOperationException: оно выбрасывается, если запрашиваемая операция не поддерживается. We use это. Так выглядит новая реализация класса Middleware:

public class Middleware implements Excuse {

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) {
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse();
   }

   @Override
   public void likeExcuse(String excuse) {
       throw new UnsupportedOperationException("Метод likeExcuse не поддерживается в новом функционале");
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Метод обращается за дополнительной информацией к БД,
       // Затем передает ее в метод dislikeExcuse an object superStudentExcuse.
   }
}
На первый взгляд это решение не кажется удачным, но имитирование функционала может привести к более сложной ситуации. Если клиент будет внимателен, а адаптер — хорошо documentирован, такое решение приемлемо.

Когда использовать Адаптер

  1. Если нужно использовать сторонний класс, но его интерфейс не совместим с основным приложением. На примере выше видно, How создается an object-прокладка, который оборачивает вызовы в понятный для целевого an object формат.

  2. Когда у нескольких существующих подклассов должен быть общий функционал. Вместо дополнительных подклассов (их создание приведет к дублированию codeа) лучше использовать адаптер.

Преимущества и недостатки

Преимущество: Адаптер скрывает от клиента подробности обработки requestов от одного an object к другому. Клиентский code не думает о форматировании данных or обработке вызовов целевого метода. Это слишком сложно, а программисты ленивые :) Недостаток: Кодовая база проекта усложняется дополнительными классами, а при большом количестве несовместимых точек их количество может вырасти до неконтролируемых размеров.

Не путать с Фасадом и Декоратором

При поверхностном изучении Адаптер можно перепутать с паттернами Фасад и Декоратор. Отличие Адаптера от Фасада заключается в том, что Фасад внедряет новый интерфейс и оборачивает целую подсистему. Ну а Декоратор, в отличие от Адаптера, меняет сам an object, а не интерфейс.

Пошаговый алгоритм реализации

  1. Для начала убедись, что есть проблема, которую может решить этот паттерн.

  2. Определи клиентский интерфейс, от имени которого будет использоваться другой класс.

  3. Реализуй класс адаптера на базе интерфейса, определенного на предыдущем шаге.

  4. В классе адаптера сделай поле, в котором хранится link на an object. Эта link передается в конструкторе.

  5. Реализуй в адаптере все методы клиентского интерфейса. Метод может:

    • Передавать вызов без изменения;

    • Изменять данные, увеличивать/уменьшать количество вызовов целевого метода, дополнительно расширять состав данных и тд.

    • В крайнем случае, при несовместимости конкретного метода, выбросить исключение UnsupportedOperationException, которое строго нужно заdocumentировать.

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

Само собой, паттерн проектирования — это не панацея от всех бед, но с его помощью можно элегантно решить задачу несовместимости an objectов с разными интерфейсами. Разработчик, знающий базовые паттерны, — на несколько ступенек выше тех, кто просто умеет писать алгоритмы, ведь они нужны для создания серьезных приложений. Повторно использовать code становится не так сложно, а поддерживать — одно удовольствие. На сегодня все! Но мы скоро продолжим знакомство с разными шаблонами проектирования :)
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION