CodeGym /Java блог /Случаен /Какви проблеми решава шаблонът за проектиране на адаптер?...
John Squirrels
Ниво
San Francisco

Какви проблеми решава шаблонът за проектиране на адаптер?

Публикувано в групата
Разработката на софтуер се затруднява от несъвместими компоненти, които трябва да работят заедно. Например, ако трябва да интегрирате нова библиотека със стара платформа, написана в по-ранни версии на Java, може да срещнете несъвместими обекти or по-скоро несъвместими интерфейси. Какви проблеми решава шаблонът за проектиране на адаптер?  - 1Какво да направите в този случай? Пренаписване на codeа? Не можем да направим това, защото анализирането на системата ще отнеме много време or вътрешната логика на приложението ще бъде нарушена. За да се реши този проблем, беше създаден моделът на адаптера. Помага на обекти с несъвместими интерфейси да работят заедно. Да видим How да го използваме!

Повече за проблема

Първо, ще симулираме поведението на старата система. Да предположим, че генерира извинения за закъснение за работа or уч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[] excuses = {"in an incredible confluence of circumstances, I ran out of hot water and had to wait until sunlight, focused using a magnifying glass, heated a mug of water so that I could wash.",
   "the artificial intelligence in my alarm clock failed me, waking me up an hour earlier than normal. Because it is winter, I thought it was still nighttime and I fell back asleep. Everything after that is a bit hazy.",
   "my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia."};
   private String [] apologies = {"This will not happen again, of course. I'm very sorry.", "I apologize for my unprofessional behavior.", "There is no excuse for my actions. I am not worthy of this position."};

   @Override
   public String generateExcuse() { // Randomly select an excuse from the array
       String result = "I was late today because " + excuses[(int) Math.round(Math.random() + 1)] + "\\n" +
               apologies[(int) Math.round(Math.random() + 1)];
       return result;
   }

   @Override
   public void likeExcuse(String excuse) {
       // Duplicate the element in the array so that its chances of being chosen are higher
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Remove the item from the array
   }
}
Нека тестваме нашия пример:

Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Изход:

"I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
I apologize for my unprofessional behavior.
Сега си представете, че сте стартирали услуга за генериране на извинения, събрали сте статистика и сте забелязали, че повечето от вашите потребители са студенти. За да обслужвате по-добре тази група, помолихте друг разработчик да създаде система, която генерира извинения специално за студенти. Екипът за разработка проведе проучване на пазара, класира извиненията, свърза малко изкуствен интелект и интегрира услугата с отчети за трафика, прогнози за времето и т.н. Сега имате библиотека за генериране на извинения за студенти, но тя има различен интерфейс: 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() {
       // Logic for the new functionality
       return "An incredible excuse adapted to the current weather conditions, traffic jams, or delays in public transport schedules.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Adds the reason to a blacklist
   }
}
Кодът не може да се променя. Текущата йерархия на класовете изглежда така: Какви проблеми решава шаблонът за проектиране на адаптер?  - 2Тази version на системата работи само с интерфейса Excuse. Не можете да пренапишете codeа: в голямо приложение извършването на такива промени може да се превърне в дълъг процес or да наруши логиката на приложението. Можем да въведем базов интерфейс и да разширим йерархията: Какви проблеми решава шаблонът за проектиране на адаптер?  - 3За да направим това, трябва да преименуваме Excuseинтерфейса. Но допълнителната йерархия е нежелателна при сериозни applications: въвеждането на общ коренов елемент нарушава архитектурата. Трябва да внедрите междинен клас, който ще ни позволи да използваме Howто новата, така и старата функционалност с минимални загуби. Накратко, имате нужда от адаптер .

Принципът зад модела на адаптера

Адаптерът е междинен обект, който позволява извикванията на метода на един обект да бъдат разбрани от друг. Нека внедрим адаптер за нашия пример и да го наречем Middleware. Нашият адаптер трябва да реализира интерфейс, който е съвместим с един от обектите. Нека бъде Excuse. Това позволява Middlewareда се извикат методите на първия обект. Middlewareприема повиквания и ги препраща по съвместим начин към втория обект. Ето реализацията Middlewareс методите generateExcuseи dislikeExcuse:

public class Middleware implements Excuse { // 1. Middleware becomes compatible with WorkExcuse objects via the Excuse interface

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) { // 2. Get a reference to the object being adapted
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse(); // 3. The adapter implements an interface method
   }

    @Override
    public void dislikeExcuse(String excuse) {
        // The method first adds the excuse to the blacklist,
        // Then passes it to the dislikeExcuse method of the superStudentExcuse object.
    }
   // The likeExcuse method will appear later
}
Тестване (в клиентски code):

public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // We create objects of the classes
       StudentExcuse newExcuse = new SuperStudentExcuse(); // that must be compatible.
       System.out.println("An ordinary excuse for an employee:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Wrap the new functionality in the adapter object
       System.out.println("Using new functionality with the adapter:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // The adapter calls the adapted method
   }
}
Изход:

An ordinary excuse for an employee:
I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
There is no excuse for my actions. I am not worthy of this position. Using new functionality with the adapter:
Невероятно извинение, адаптирано към текущите метеорологични условия, задръствания or закъснения в разписанията на градския транспорт. Методът generateExcuseпросто предава извикването на друг обект, без ниHowви допълнителни промени. Методът dislikeExcuseизискваше първо да поставим извинението в черен списък. Възможността за извършване на междинна обработка на данни е причина хората да обичат модела на адаптера. Но Howво да кажем за likeExcuseметода, който е част от Excuseинтерфейса, но не и част от StudentExcuseинтерфейса? Новата функционалност не поддържа тази операция. Измислен е UnsupportedOperationExceptionза тази ситуация. Изхвърля се, ако заявената операция не се поддържа. Нека го използваме. Ето How 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("The likeExcuse method is not supported by the new functionality");
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // The method accesses a database to fetch additional information,
       // and then passes it to the superStudentExcuse object's dislikeExcuse method.
   }
}
На пръв поглед това решение не изглежда много добро, но имитирането на функционалността може да усложни ситуацията. Ако клиентът обърне внимание и адаптерът е добре documentиран, такова решение е приемливо.

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

  1. Когато трябва да използвате клас на трета страна, но интерфейсът му е несъвместим с основното приложение. Примерът по-горе показва How да създадете адаптерен обект, който обвива повикванията във формат, който целевият обект може да разбере.

  2. Когато няколко съществуващи подкласа се нуждаят от обща функционалност. Вместо да създавате допълнителни подкласове (което ще доведе до дублиране на code), по-добре е да използвате адаптер.

Предимства и недостатъци

Предимство: Адаптерът скрива от клиента подробностите за обработката на заявки от един обект към друг. Кодът на клиента не мисли за форматиране на данни or обработка на извиквания към целевия метод. Твърде сложно е, а програмистите са мързеливи :) Недостатък: Кодовата база на проекта е усложнена от допълнителни класове. Ако имате много несъвместими интерфейси, броят на допълнителните класове може да стане неуправляем.

Не бъркайте адаптер с фасада or декоратор

Само с повърхностен оглед адаптерът може да бъде объркан с шарките на фасадата и декоратора. Разликата между адаптер и фасада е, че фасадата въвежда нов интерфейс и обгръща цялата подсистема. А декораторът, за разлика от адаптера, променя самия обект, а не интерфейса.

Алгоритъм стъпка по стъпка

  1. Първо, уверете се, че имате проблем, който този модел може да разреши.

  2. Дефинирайте клиентския интерфейс, който ще се използва за индиректно взаимодействие с несъвместими обекти.

  3. Накарайте класа на адаптера да наследи интерфейса, дефиниран в предишната стъпка.

  4. В класа на адаптера създайте поле за съхраняване на препратка към адаптирания обект. Тази препратка се предава на конструктора.

  5. Внедрете всички методи на клиентския интерфейс в адаптера. Един метод може:

    • Предавайте повиквания, без да правите промени

    • Променете or допълнете данните, увеличете/намалете броя на извикванията към целевия метод и др.

    • В екстремни случаи, ако определен метод остане несъвместим, хвърлете UnsupportedOperationException. Неподдържаните операции трябва да бъдат строго documentирани.

  6. Ако приложението използва само класа на адаптера през клиентския интерфейс (Howто в примера по-горе), тогава адаптерът може да бъде безболезнено разширен в бъдеще.

Разбира се, този модел на проектиране не е панацея за всички болести, но може да ви помогне да решите елегантно проблема с несъвместимостта между обекти с различни интерфейси. Разработчикът, който познава основните модели, е няколко стъпки пред тези, които знаят само How да пишат алгоритми, тъй като шаблоните за проектиране са необходими за създаване на сериозни applications. Повторното използване на codeа не е толкова трудно, а поддръжката става приятна. Това е всичко за днес! Но скоро ще продължим да се запознаваме с различни дизайнерски модели :)
Коментари
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION