Патерн адаптер: опис, функції і можливості, поради по роботі

Адаптер - це структурний патерн проектування, який використовується для організації та реалізації методів об`єкта, який не можна модифікувати за коштами спеціально розробленого інтерфейсу. Інакше можна сказати, що це структурний шаблон, який дає можливість об`єктам з несумісними інтерфейсами взаємодіяти між собою.

Опис

Патерн Адаптер здійснює адаптацію між класами і об`єктами. Як і будь-який адаптер в навколишньому світі, шаблон є інтерфейсом або мостом між двома об`єктами. У реальному світі у нас є адаптери для блоків живлення, для жорстких дисків, для навушників, для карт пам`яті камери і так далі. Для прикладу розглянемо кілька адаптерів для карт пам`яті. Якщо не вдається підключити карту пам`яті камери до ноутбука безпосередньо, можна використовувати адаптер: карта пам`яті камери підключається до адаптера, а адаптер-до роз`єму для ноутбука. Таким чином проблема несумісності інтерфейсів буде вирішена.

Адаптер: шаблон дизайну та інтерфейс

У разі розробки програмне забезпечення все йде приблизно таким же чином. Можна уявити ситуацію, коли є якийсь клас, який очікує на якийсь тип об`єкта, і є об`єкт, що пропонує той же функціонал, але з іншим інтерфейсом. Звичайно, вигідно буде використовувати обидва з них, щоб не реалізовувати один з інтерфейсів повторно і не змінювати існуючі класи. Саме в такій ситуації буде розумно використовувати адаптер для проектування програмного забезпечення.

Реалізація

На малюнку нижче показана діаграма КЛАСІВ UML (ЮМЛ) патерну Адаптер.

Адаптер: діаграма UML

Класи та об`єкти, що беруть участь у шаблоні проектування:

  1. (Target) - визначає специфічний для домену інтерфейс, який використовує Client.
  2. (Adapter) - адаптує інтерфейс (Adaptee) до цільового інтерфейсу.
  3. (Adaptee) - визначає існуючий інтерфейс, який потрібно адаптувати.
  4. (Client) - взаємодіє з об`єктами, відповідними інтерфейсу (Target).

Застосування

Патерн Адаптер використовується в наступних випадках:

  • Коли існує клас (Target), який викликає методи, визначені в інтерфейсі. Крім того, існує інший клас (Adapter), який не реалізує інтерфейс, але реалізує операції та методи, які слід викликати з першого класу через інтерфейс. У програміста немає можливості змінити жоден з існуючих кодів. Адаптер реалізує свій інтерфейс і стане мостом між двома класами.
Патерн Адаптер: побудова проекту
  • Коли під час написання класу (Target) для загального використання важливо спиратися на деякі загальні інтерфейси, а розробник має деякі реалізовані класи, які не реалізують інтерфейс. Також цей клас (Target) повинен бути викликаним.

Хорошим прикладом для застосування адаптера можуть служити оболонки, використовувані для прийняття сторонніх бібліотек і структур: більшість додатків, що використовують сторонні бібліотеки, вживають адаптер в якості проміжного рівня між додатком і сторонньої бібліотекою для відділення програми від бібліотеки. Якщо потрібно використовувати іншу бібліотеку, для нової бібліотеки потрібен лише адаптер без необхідності зміни коду програми.

Адаптери об`єктів на основі делегування

Об`єкт (Adapter) є класичним прикладом шаблону адаптера. Він використовує композицію, а (Adaptee) делегує виклики самому собі, що недоступно адаптерам класів, які розширюють (Adaptee). Така поведінка дає нам кілька переваг перед адаптерами КЛАСІВ, однак адаптери класів можуть бути реалізовані мовами, що дозволяють множинне успадкування. Основною перевагою є те, що (Adapter) адаптує не тільки (Adaptee), але і всі його підкласи. Всі ці підкласи існують з одним "невеликий" обмеженням: усі вони не можуть додавати нові методи, оскільки використовуваним механізмом є делегування. Таким чином, для будь-якого нового методу адаптер повинен бути змінений або Розширений для надання нових методів. Основним недоліком є те, що він вимагає написання нового коду для делегування всіх необхідних запитів адаптеру.

Адаптери класу на основі (множинного) успадкування

Адаптери класів можуть бути реалізовані на мовах, що підтримують множинне успадкування. Мови програмування Java, C# або PHP не підтримують множинне успадкування, проте мають інтерфейси. Таким чином, такі шаблони не можуть бути легко реалізовані в цих мовах. Хорошим прикладом мови програмування, де можна з легкістю реалізувати проектування, є мова C.

Патерн Адаптер використовує успадкування замість композиції. Це означає, що замість того, щоб делегувати дзвінки (Adaptee), він успадковує його. На закінчення всього адаптер класу повинен розділити на підкласи і (Target), і сам (Adapter).

Адаптер на основі множинного успадкування

При такому підході є свої переваги та недоліки:

  • Шаблон адаптує певний клас (Adaptee). Клас розширює цю адаптацію. Якщо це підклас, він не може бути адаптований існуючим адаптером.
  • Шаблон не вимагає весь код, необхідний для делегування, який повинен бути написаний для класу (Adapter).
  • Якщо об`єкт (Target) представлений інтерфейсом, а не класом, ми можемо говорити про "класовий" адаптерах, оскільки ми можемо реалізувати стільки інтерфейсів, скільки хочемо.

Двосторонні адаптери

Двосторонні адаптери-це адаптери, які реалізують обидва інтерфейси: і (Target), і (Adaptee). Адаптований об`єкт може використовуватися в якості (Target) в нових системах, що працюють з класами (Target), або в якості (Adaptee) в інших системах, працюючих з класами (Adaptee). Якщо піти далі в цьому напрямку, то у нас можуть бути адаптери, що реалізують N-ве число інтерфейсів, адаптуються до n-систем. Двосторонні Адаптери та N-смугові адаптери важко реалізувати в системах, які не підтримують багаторазове успадкування. Якщо адаптер повинен розширювати клас (Target), він не може розширювати інший клас, такий як (Adaptee), тому (Adaptee) повинен бути інтерфейсом, і всі дзвінки можуть бути делеговані від адаптера об`єкту (Adaptee).

Використання шаблону Адаптер в VR-девелопменті

Крім того, якщо (Target) і (Adapter) схожі, то адаптер повинен просто делегувати запити від класу (Target) до класу (Adapter), а якщо (Target) і (Adaptee) не схожі один на одного, то адаптеру може знадобитися перетворити структури даних між ними і реалізувати операції, необхідні для (Target), але не реалізовані в класі (Adaptee).

Приклад реалізації

Припустимо, у нас є клас (Bird) з методами fly () та makeSound (). А також клас (ToyDuck) з методом Squeak (). Скажімо, у нас мало об`єктів (ToyDuck) і ми хочемо використовувати об`єкти (Bird) замість них. Птахи мають подібну функціональність, але реалізують інший інтерфейс, тому ми не можемо використовувати їх безпосередньо. Тому ми будемо використовувати шаблон адаптер. Тут наш (Client) буде (ToyDuck), а (Adaptee)- (Bird). Нижче наведено приклад реалізації проектування патерну Адаптер на Java, одному з найпоширеніших мова програмування.

interfaceBird{publicvoidfly();publicvoidmakeSound();}classSparrowimplementsBird{publicvoidfly(){System.out.println("Flying");}publicvoidmakeSound(){System.out.println("Chirp Chirp");}}interfaceToyDuck{publicvoidsqueak();}classPlasticToyDuckimplementsToyDuck{publicvoidsqueak(){System.out.println("Squeak");}}classBirdAdapterimplementsToyDuck{Bird bird;publicBirdAdapter(Bird bird){this.bird = bird;}publicvoidsqueak(){bird.makeSound();}}classMain{publicstaticvoidmain(String args[]){Sparrow sparrow =newSparrow();ToyDuck toyDuck =newPlasticToyDuck();ToyDuck birdAdapter =newBirdAdapter(sparrow);System.out.println("Sparrow...");sparrow.fly();sparrow.makeSound();System.out.println("ToyDuck...");toyDuck.squeak();System.out.println("BirdAdapter...");birdAdapter.squeak();}}

Припустимо, у нас є птах, здатний робити Sound (), і пластикова Іграшкова качка, яка може пищати - Squeak (). Тепер припустимо, що наш (Client) змінює вимогу і хоче, щоб (ToyDuck) виконав Sound (), але як?

Приклад проектування на основі патерну Адаптер

Рішення полягає в тому, що ми просто змінимо клас реалізації на новий клас адаптера і скажемо клієнту передати екземпляр птаха цьому класу. Ось і все. Тепер змінивши лише один рядок, ми навчимо (ToyDuck) твітувати, як горобець.

Статті на тему