JavaRush /Blog Java /Random-PL /Jakie problemy rozwiązuje wzorzec projektowy adaptera?

Jakie problemy rozwiązuje wzorzec projektowy adaptera?

Opublikowano w grupie Random-PL
Tworzenie oprogramowania jest często komplikowane przez niezgodność między współpracującymi ze sobą komponentami. Przykładowo, jeśli zajdzie potrzeba zintegrowania nowej biblioteki ze starą platformą napisaną we wcześniejszych wersjach Javy, możesz napotkać niekompatybilność obiektów, a dokładniej interfejsów. Co zrobić w tym przypadku? Przepisać kod? Ale to jest niemożliwe: analiza systemu zajmie dużo czasu, w przeciwnym razie wewnętrzna logika pracy zostanie zerwana. Jakie problemy rozwiązuje wzorzec projektowy Adaptera - 1Aby rozwiązać ten problem, opracowano wzorzec Adapter, który pomaga obiektom z niekompatybilnymi interfejsami współpracować. Zobaczmy, jak z niego korzystać!

Więcej szczegółów problemu

Najpierw zasymulujmy zachowanie starego systemu. Załóżmy, że generuje powody spóźnienia się do pracy lub szkoły. Aby to zrobić, mamy interfejs Excusezawierający metody generateExcuse()i likeExcuse().dislikeExcuse()
public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
Interfejs ten jest implementowany przez klasę WorkExcuse:
public class WorkExcuse implements Excuse {
   private String[] reasonOptions = {"по невероятному стечению обстоятельств у нас в доме закончилась горячая вода и я ждал, пока солнечный свет, сконцентрированный через лупу, нагреет кружку воды, чтобы я мог умыться.",
   "искусственный интеллект в моем будильнике подвел меня и разбудил на час раньше обычного. Поскольку сейчас зима, я думал что еще ночь и уснул. Дальше все Jak в тумане.",
   "предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице."};
   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) {
       // Удаляем элемент из массива
   }
}
Przetestujmy przykład:
Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Wniosek:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице. 
Прошу меня извинить за непрофессиональное поведение.
A teraz wyobraźmy sobie, że uruchomiłeś usługę, zebrałeś statystyki i zauważyłeś, że większość użytkowników serwisu to studenci. Aby ulepszyć go na potrzeby tej grupy, specjalnie dla niej zamówiłeś u innego dewelopera system generowania wymówek. Zespół programistów przeprowadził badania, opracował oceny, połączył sztuczną inteligencję, dodał integrację z korkami, pogodą i tak dalej. Teraz masz bibliotekę do generowania wymówek dla uczniów, ale interfejs interakcji z nią jest inny - StudentExcuse:
public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
Interfejs posiada dwie metody: generateExcuse, która generuje wymówkę oraz dislikeExcuse, która blokuje wymówkę, aby nie pojawiała się ona w przyszłości. Biblioteka innej firmy jest zamknięta do edycji - nie można zmienić jej kodu źródłowego. W rezultacie w Twoim systemie znajdują się dwie klasy implementujące interfejs Excuseoraz biblioteka z klasą SuperStudentExcuseimplementującą interfejs StudentExcuse:
public class SuperStudentExcuse implements StudentExcuse {
   @Override
   public String generateExcuse() {
       // Логика нового функционала
       return "Невероятная отговорка, адаптированная под текущее состояние погоды, пробки Lub сбои в расписании общественного транспорта.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Добавляет причину в черный список
   }
}
Kodu nie można zmienić. Obecny schemat będzie wyglądał następująco: Jakie problemy rozwiązuje wzorzec projektowy Adapter - 2?Ta wersja systemu działa tylko z interfejsem Excuse. Nie możesz przepisać kodu: w dużej aplikacji takie zmiany mogą zająć dużo czasu lub złamać logikę aplikacji. Możesz zasugerować wprowadzenie głównego interfejsu i zwiększenie hierarchii: Jakie problemy rozwiązuje wzorzec projektowy Adaptera - 3Aby to zrobić, musisz zmienić nazwę interfejsu Excuse. Jednak w poważnych zastosowaniach dodatkowa hierarchia jest niepożądana: wprowadzenie wspólnego elementu głównego psuje architekturę. Należy zaimplementować klasę pośrednią, która pozwoli na wykorzystanie nowej i starej funkcjonalności przy minimalnych stratach. Krótko mówiąc, potrzebujesz adaptera .

Jak działa wzorzec Adaptera

Adapter to obiekt pośredni, który sprawia, że ​​wywołania metod jednego obiektu są zrozumiałe dla drugiego. Zaimplementujmy adapter dla naszego przykładu i nazwijmy go Middleware. Nasz adapter musi implementować interfejs zgodny z jednym z obiektów. Niech będzie Excuse. Dzięki temu Middlewaremoże wywoływać metody pierwszego obiektu. Middlewareodbiera wywołania i przekazuje je do drugiego obiektu w kompatybilnym formacie. Tak wygląda implementacja metody Middlewarez metodami generateExcusei dislikeExcuse:
public class Middleware implements Excuse { // 1. Middleware становится совместимым с obiektом WorkExcuse через интерфейс Excuse

   private StudentExcuse superStudentExcuse;

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

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

    @Override
    public void dislikeExcuse(String excuse) {
        // Метод предварительно помещает отговорку в черный список БД,
        // Затем передает ее в метод dislikeExcuse obiektа superStudentExcuse.
    }
   // Метод likeExcuse появятся позже
}
Testowanie (w kodzie klienta):
public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // Создаются obiektы классов,
       StudentExcuse newExcuse = new SuperStudentExcuse(); // Которые должны быть совмещены.
       System.out.println("Обычная причина для работника:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Оборачиваем новый функционал в obiekt-адаптер
       System.out.println("Использование нового функционала с помощью адаптера:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // Адаптер вызывает адаптированный метод
   }
}
Wniosek:
Обычная причина для работника:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице.
Нет оправдания моему поступку. Я недостоин этой должности. Использование нового функционала с помощью адаптера
Niesamowita wymówka, dostosowana do aktualnego stanu pogody, korków czy zakłóceń w rozkładach komunikacji miejskiej. Metoda generateExcusepo prostu przenosi wywołanie do innego obiektu, bez dodatkowych przekształceń. Metoda dislikeExcusewymagała uprzedniego umieszczenia wymówki na czarnej liście bazy danych. Dodatkowe pośrednie przetwarzanie danych jest powodem, dla którego wzór Adapter jest tak lubiany. Ale co z metodą likeExcuse, która jest w interfejsie Excuse, ale jej nie ma StudentExcuse? Ta operacja nie jest obsługiwana w nowej funkcjonalności. W tym przypadku wymyślono wyjątek UnsupportedOperationException: jest on zgłaszany, jeśli żądana operacja nie jest obsługiwana. Wykorzystajmy to. Tak wygląda implementacja nowej klasy 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 obiektа superStudentExcuse.
   }
}
Na pierwszy rzut oka rozwiązanie to nie wydaje się udane, jednak symulowanie funkcjonalności może prowadzić do bardziej złożonej sytuacji. Jeśli klient jest uważny i adapter jest dobrze udokumentowany, to rozwiązanie jest do przyjęcia.

Kiedy używać adaptera

  1. Jeśli chcesz użyć klasy innej firmy, ale jej interfejs nie jest kompatybilny z aplikacją główną. Powyższy przykład pokazuje, jak tworzony jest obiekt podkładki, który otacza wywołania w formacie zrozumiałym dla obiektu docelowego.

  2. Gdy kilka istniejących podklas musi mieć wspólną funkcjonalność. Zamiast dodatkowych podklas (ich utworzenie będzie prowadzić do powielania kodu), lepiej zastosować adapter.

Zalety i wady

Zaleta: Adapter ukrywa przed klientem szczegóły przetwarzania żądań z jednego obiektu do drugiego. Kod klienta nie myśli o formatowaniu danych ani obsłudze wywołań metody docelowej. Jest to zbyt skomplikowane, a programiści są leniwi :) Wada: Baza kodu projektu jest skomplikowana przez dodatkowe klasy i w przypadku dużej liczby niekompatybilnych punktów ich liczba może urosnąć do niekontrolowanych rozmiarów.

Nie mylić z fasadą i dekoratorem

Po powierzchownym zbadaniu Adapter można pomylić z wzorami Fasada i Dekorator. Różnica między adapterem a fasadą polega na tym, że fasada wprowadza nowy interfejs i otacza cały podsystem. Cóż, Dekorator, w przeciwieństwie do Adaptera, zmienia sam obiekt, a nie interfejs.

Algorytm realizacji krok po kroku

  1. Najpierw upewnij się, że istnieje problem, który ten wzór może rozwiązać.

  2. Zdefiniuj interfejs klienta, w imieniu którego będzie używana inna klasa.

  3. Zaimplementuj klasę adaptera w oparciu o interfejs zdefiniowany w poprzednim kroku.

  4. W klasie adaptera utwórz pole przechowujące referencję do obiektu. To odwołanie jest przekazywane w konstruktorze.

  5. Zaimplementuj wszystkie metody interfejsu klienta w adapterze. Metoda może:

    • Przekaż połączenie bez modyfikacji;

    • Zmień dane, zwiększ/zmniejsz liczbę wywołań metody docelowej, dalej rozszerzaj skład danych itp.

    • W ostateczności, jeśli dana metoda jest niezgodna, zgłoś wyjątek UnsupportedOperationException, który należy ściśle udokumentować.

  6. Jeśli aplikacja korzysta z adaptera wyłącznie poprzez interfejs klienta (jak w powyższym przykładzie), umożliwi to bezproblemową rozbudowę adapterów w przyszłości.

Oczywiście wzorzec projektowy nie jest panaceum na wszystkie bolączki, ale za jego pomocą można w elegancki sposób rozwiązać problem niekompatybilności obiektów z różnymi interfejsami. Programista znający podstawowe wzorce jest kilka kroków wyżej od tych, którzy po prostu umieją pisać algorytmy, bo są one potrzebne do tworzenia poważnych aplikacji. Ponowne wykorzystanie kodu staje się mniej trudne, a utrzymanie staje się przyjemnością. To wszystko na dzisiaj! Ale już niedługo będziemy kontynuować naszą znajomość różnych wzorców projektowych :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION