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

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

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

Опис

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


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

Реалізація

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

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

  1. (Target) - визначає специфічний для домену інтерфейс, який використовує Client.
  2. (Adapter) - адаптує інтерфейс (Adaptee) до цільового інтерфейсу.
  3. (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).

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

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

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

interface Bird
{
    public void fly();
    public void makeSound();
}
 
class Sparrow implements Bird
{
    public void fly()
    {
        System.out.println(""Flying"");
    }
    public void makeSound()
    {
        System.out.println(""Chirp Chirp"");
    }
}
 
interface ToyDuck
{
    public void squeak();
}
 
class PlasticToyDuck implements ToyDuck
{
    public void squeak()
    {
        System.out.println(""Squeak"");
    }
}
 
class BirdAdapter implements ToyDuck
{
    Bird bird;
    public BirdAdapter(Bird bird)
    {
        this.bird = bird;
    }
 
    public void squeak()
    {
        bird.makeSound();
    }
}
 
class Main
{
    public static void main(String args[])
    {
        Sparrow sparrow = new Sparrow();
        ToyDuck toyDuck = new PlasticToyDuck();
        ToyDuck birdAdapter = new BirdAdapter(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) чирикати, як горобець.