Patterns. Паттерн Adapter. Огляд та дослідження. Приклади реалізації на мові C++




Паттерн Adapter. Огляд та дослідження. Приклади реалізації на мові C++


Зміст


Пошук на інших ресурсах:

1. Призначення паттерну Адаптер (Adapter)

Паттерн Adapter належить до структурних паттернів і використовується для структурування класів та об’єктів. Необхідність використання паттерну Adapter виникає у випадках, коли потрібно привести (адаптувати) одну систему до вимог іншої системи.

Паттерн Adapter може виконувати не тільки прості перетворення (наприклад, зміна імен) але і реалізовувати зовсім інший набір операцій (підміна одного коду на інший).

 

2. В яких випадках застосовується паттерн Адаптер? Випадки застосування паттерну Адаптер

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

  • коли в розроблювану програму потрібно включити деякий клас, але інтерфейс цього класу не відповідає вимогам програми;
  • коли в програмі потрібно створити клас, який використовується повторно. У цьому випадку клас повинен взаємодіяти з іншими (невідомими) класами, що мають несумісні з програмою інтерфейси;
  • коли необхідно використати декілька існуючих підкласів в одному класі. Це випадок, коли застосовується адаптер об’єктів для пристосування цих підкласів до спільного батьківського класу.

 

3. Структура паттерну Адаптер (рисунок)

Паттер Адаптер є двох видів:

  • для класу (рисунок 1). У цьому випадку застосовується множинна спадковість для адаптації одного інтерфейсу до іншого;
  • для об’єкту (рисунок 2). У цьому випадку застосовується композиція об’єктів.

На діаграмах, зображених на рисунку 1 та рисунку 2 схематично зображено особливості реалізації паттерну Adapter. Клас Adaptee – це клас, що містить метод SpecificRequest(), який потрібно адаптувати до потреб клієнта (Client), що використовує метод Request(). Метод Request() оголошується в інтерфейсі Target, посилання на який отримує Client. Між інтерфейсом Target та класом Adaptee створюється зв’язуючий клас Adapter, який виконує необхідні перетворення.

Через інтерфейс Target клієнт Client звертається до методів, реалізовних в класі Adapter. Адаптер, в свою чергу викликає методи класу Adaptee.

Існує два способи реалізації класу Adapter:

  • спосіб успадкування (рисунок 1). Цей спосіб отримав назву адаптер для класу. При цьому способі клас Adapter успадковує методи класу Adaptee та реалізує методи інтерфейсу Target;
  • спосіб композиції об’єктів. Цей спосіб називається адаптер для об’єкту. При цьому способі клас Adapter використовує посилання (екземляр, об’єкт) типу Adaptee.

Структура паттерну Adapter для класу

Рисунок 1. Структура паттерну Adapter для класу

Структура паттерну Adapter для об’єкту

Рисунок 2. Структура паттерну Adapter для об’єкту

 

4. Приклад реалізації паттерну Adapter для класу на мові C++

У прикладі реалізовано адаптер для класу на мові C++, що відповідає рисунку 1 даної теми.

#include <iostream>
using namespace std;

// Паттерн: Адаптер для класу.
// Адаптує клас Adaptee до класу Target через проміжний клас Adapter.
// Абстрактний клас Target
class Target
{
public:
  virtual void Request() = 0;
};

// Клас, метод якого потрібно адаптувати до іншої системи.
// У даному випадку адаптується ім'я методу SpecificRequest() в метод Request()
class Adaptee
{
public:
  // Деякий специфічний метод
  void SpecificRequest()
  {
    cout << "Adaptee.SpecificRequest()" << endl;
  }
};

// Клас Adapter - використовує множинну спадковість
class Adapter :public Target, public Adaptee
{
public:
  void Request()
  {
    // Викликає SpecificRequest() з класу Adaptee
    SpecificRequest();
  }
};

// Клас Client - отримує посилання (або покажчик) на Target
class Client
{
public:
  // отримує посилання на Target
  void ClientMethod(Target& target)
  {
    target.Request();
  }

  // отримує покажчик на Target
  void ClientMethod(Target* target)
  {
    target->Request();
  }
};

void main()
{
  // Демонстрація паттерну Адаптер для класу.
  // потрібно адаптувати метод SpecificRequest() класу Adaptee до потреб екземпляру client
  Client client;
  Target* target = (Target*) new Adapter();

  // передати клієнту екземпляр adaptee через інтерфейс target
  client.ClientMethod(target); // Adaptee.SpecificRequest()
}

 

5. Приклад реалізації паттерну Adapter для об’єкту на мові C++

У прикладі, на мові C++ реалізовано паттерн Adapter для об’єкту, що відповідає рисунку 2 даної теми.

#include <iostream>
using namespace std;

// Паттерн: Адаптер для об'єкту.
// Абстрактний клас Target
class Target
{
public:
  virtual void Request() = 0;
};

// Клас, в якому метод SpecificRequest() потрібно передати клієнту як метод Request()
class Adaptee
{
public:
  // Деякий специфічний метод
  void SpecificRequest()
  {
    cout << "Adaptee.SpecificRequest()" << endl;
  }
};

// Клас Adapter - використовує композицію об'єктів
class Adapter :public Target
{
private:
  Adaptee* adaptee;

public:
  // Конструктор приймає посилання на adaptee
  Adapter(Adaptee* _adaptee) :adaptee(_adaptee)
  {}

  void Request()
  {
    // Викликає SpecificRequest() з класу Adaptee
    adaptee->SpecificRequest();
  }
};

// Клас Client - отримує посилання (або покажчик) на Target
class Client
{
public:
  // отримує посилання на Target
  void ClientMethod(Target& target)
  {
    target.Request();
  }

  // отримує покажчик на Target
  void ClientMethod(Target* target)
  {
    target->Request();
  }
};

void main()
{
  // Демонстрація паттерну Адаптер для об'єкту
  // Екземпляр класу Client, який має отримати код методу SpecificRequest() класу Adaptee
  Client client;

  // Екземпляр, який містить метод SpecificRequest()
  Adaptee adaptee;

  // Посилання на базовий клас, яке буде передаватись клієнту
  Target* target;

  // конвертування adaptee->target через зв'язуючий клас Adapter
  target = new Adapter(&adaptee);

  // Передати клієнту target
  client.ClientMethod(target);
}

 

6. Результати паттерну Adapter для класу. Приклади на мові C++

Якщо паттерн Адаптер застосовується для класу (рисунок 1), то можна отримати наступні результати.

  1. У випадку, якщо одночасно потрібно адаптувати клас та його підкласи, паттерн працювати не буде. Тому що при успадкуванні дії передоручаються конкретному (тільки одному) класу Adaptee (дивіться приклад 1).
  2. Існує можливість замінити деякі операції адаптованого класу Adaptee на інші. Це зв’язано з тим, що клас Adapter є підкласом класу Adaptee (використовується успадкування, дивіться приклад 2).
  3. Паттерн вводить тільки один новий об’єкт (дивіться приклад 3).

У нижченаведених прикладах демонструються усі перераховані результати паттерну Adapter для класу.

Приклад 1.У прикладі   демонструється неможливість реалізувати паттерн Adapter для класу, якщо класи утворюють ієрархію.

...

// Адаптер для класу.
// Паттерн не працює, якщо одночасно адаптувати декілька класів, які утворюють ієрархію.
// Клас A - базовий для класу B
class A
{
public:
  virtual void SpecifiedRequest()
  {
    cout << "A.SpecifiedRequest()" << endl;
  }
};

// Клас B - успадкований від A
class B :A
{
public:
  virtual void SpecifiedRequest()
  {
    cout << "B.SpecifiedRequest()" << endl;
  }
};

// Клас адаптер - успадковує класи A, B
class Adapter : private A, private B, public ITarget
{
public:
  void Request()
  {
    // Тут помилка - неоднозначний виклик (Adapter::SpecifiedRequest is ambiguous)
    // незрозуміло, якого класу метод SpecifiedRequest() потрібно викликати.
    SpecifiedRequest();
  }
};

void main(void)
{
}

 Як видно з прикладу, два класи A, B утворюють ієрархію. Клас A є базовим класом, клас B є похідним класом. У класі Adapter здійснюється спроба адаптувати метод SpecifiedRequest() класів A, B у метод Request(). Однак при такому виклику

...

void Request()
{
  // Тут помилка - неоднозначний виклик (Adapter::SpecifiedRequest is ambiguous)
  // незрозуміло, якого класу метод SpecifiedRequest() потрібно викликати.
  SpecifiedRequest();
}

...

компілятор видасть помилку

Adapter::SpecifiedRequest is ambiguous

яка означає – неоднозначний виклик. Тут потрібно вибирати один з двох викликів (класів)

A::SpecifiedRequest();

або

B::SpecifiedRequest();

Якщо в метод Request() вписати

A::SpecifiedRequest();
B::SpecifiedRequest();

то це буде виклик двох методів замість одного, що теж не підходить під ідеологію паттерну.

Щоб реалізувати можливість одночасної роботи з багатьма адаптованими об’єктами адаптер для класу потрібно замінити на адаптер для об’єкту (дивіться нижче).

Приклад 2. У прикладі демонструється фрагмент коду, в якому у методі Request() класу Adapter вписується зовсім інший код заміщення. Оскільки, при успадкуванні клас Adapter є підкласом класу Adaptee, то це дозволяє повністю замінювати операції адаптованого класу Adaptee.

// Клас адаптер - успадковує класи Adaptee, ITarget
class Adapter : private Adaptee, public ITarget
{
public:
  void Request()
  {
    // SpecifiedRequest();     - метод класу Adaptee не викликається,
    // замість нього вписується інший код
    cout << "Another program code" << endl;
  }
};

Приклад 3. У прикладі демонструється фрагмент коду клієнта, в якому показано, що не потрібно створювати об’єкт класу Adaptee, що адаптується. Це пов’язано з тим, що усі необхідні операції заміни реалізуються через механізм успадкування в класі Adapter.

...

void main()
{
  // Демонстрація паттерну Адаптер для класу.
  // потрібно адаптувати метод SpecificRequest() класу Adaptee до потреб екземпляру client
  Client client;
  Target* target = (Target*) new Adapter();

  // передати клієнту екземпляр target
  client.ClientMethod(target);
}

У вищенаведеному фрагменті коду, функція main() виступає клієнтським кодом. У функції main(), щоб добратись до класу Adaptee, не потрібно створювати екземпляру цього класу.

 

7. Результати паттерну Adapter для об’єкту. Приклади

Якщо паттерн Adapter застосовується для об’єкту (дивіться рисунок 2), то можна отримати наступні результати.

  1. Клас Adapter може працювати одночасно з багатьма об’єктами, які потрібно адаптувати. Це стосується також випадків, коли класи, що потрібно адаптувати, утворюють ієрархію (дивіться нижче приклад 1).
  2. Якщо класи, що потрібно адаптувати, утворюють ієрархію, то адаптер для об’єктів може додати нову (або змінити) функціональність до всіх об’єктів, які потрібно адаптувати.
  3. Складність у заміщенні операцій (методів) класу Adaptee, який адаптується. Щоб замістити метод (операцію) в класі Adapter, потрібно успадкувати від класу Adaptee підклас, а потім використати посилання на цей підклас, а не на Adaptee (дивіться нижче приклад 2).

Приклад 1. У прикладі демонструється можливість одночасного використання класів, які утворюють ієрархію. Нехай задано два класи, які утворюють ієрархію: базовий клас A та похідний клас B.

// Адаптер для об'єкту.
// Паттерн дозволяє одночасно адаптувати декілька класів, які утворюють ієрархію.
#include <iostream>
using namespace std;

class ITarget
{
public:
  virtual void Request() = 0;
};

// Клас A - базовий
class A
{
public:
  virtual void SpecifiedRequest()
  {
    cout << "A.SpecifiedRequest()" << endl;
  }
};

// Клас B - похідний від класу A
class B: public A
{
public:
  virtual void SpecifiedRequest()
  {
    cout << "B.SpecifiedRequest()" << endl;
  }
};

// Клас адаптер - використовує композицію об'єктів,
// успадковує клас ITarget
class Adapter : public ITarget
{
private:
  A* adaptee;

  public:
    // Конструктор, отримує посилання на вершину ієрархії,
    // за цим посиланням клієнт може передати будь-який клас в ієрархії.
    // Це є перевага паттерну Адаптер для об'єктів порівняно з адаптером для класів.
    Adapter(A* adaptee)
    {
      this->adaptee = adaptee;
    }

    virtual void Request()
    {
      // Викликає метод SpecifiedRequest()
      adaptee->SpecifiedRequest();
    }
};

// Клас Client - використовує адаптер
class Client
{
public:
  // Метод клієнта - викликає методи різних класів з ієрархії
  void ClientMethod(ITarget* target)
  {
    target->Request();
  }
};

void main(void)
{
  // 1. Передати клієнту метод SpecificRequest() класу A
  Client client; // Оголосити екземпляр класу Client

  // Оголосити екземпляр класу A
  A objA;
  Adapter adapterA(&objA);

  // Викликати клієнтський метод - передати методу екземпляр класу A
  client.ClientMethod((ITarget*)&adapterA); // A.SpecificRequest()

  // 2. Передати клієнту метод SpecificRequest() класу B
  B objB;
  Adapter adapterB((A*)&objB);
  client.ClientMethod((ITarget*)&adapterB); // B.SpecificRequest()

  // 3. Ще інший випадок використання клієнта
  ITarget* client2; // покажчик на інтерфейс ITarget
  client2 = (ITarget*)&adapterA;
  client2->Request(); // A.SpecificRequest()

  client2 = (ITarget*)&adapterB;
  client2->Request(); // B.SpecificRequest
}

Приклад 2.   У прикладі демонструється заміщення операції в класі, який потрібно адаптувати, для паттерну Adapter для об’єктів.

Щоб замістити операцію (метод) SpecificRequest() в класі Adaptee виконуються наступні дії:

  • від класу Adaptee успадковується клас AdapteeSub;
  • у класі AdapteeSub оголошується метод SpecificRequest(), який заміщає метод SpecificRequest() класу Adaptee;
  • у класі Adapter оголошується покажчик (посилання) на похідний клас AdapteeSub;
  • у класі Adapter в методі Request() викликається метод SpecificRequest() класу AdapteeSub.

 

// Адаптер для об'єкту.
// Демонстрація заміщення методу в класі Adaptee у випадку реалізації адаптеру для об'єкту.
#include <iostream>
using namespace std;

class ITarget
{
public:
  virtual void Request() = 0;
};

// Клас Adaptee - який потрібно адаптувати
class Adaptee
{
public:
  void SpecifiedRequest()
  {
    cout << "Adaptee.SpecifiedRequest()" << endl;
  }
};

// Підклас класу Adaptee
class AdapteeSub : private Adaptee
{
public:
  void SpecifiedRequest()
  {
    cout << "Another operation: AdapteeSub.SpecifiedRequest()";
  }
};

// Клас адаптер - використовує композицію об'єктів,
// успадковує клас ITarget
class Adapter : public ITarget
{
private:
  AdapteeSub* adapteeSub; // посилання на підклас

public:
  // Конструктор, отримує посилання на базовий клас Adaptee
  Adapter(Adaptee* adaptee)
  {
    this->adapteeSub = (AdapteeSub*)adaptee;
  }

  virtual void Request()
  {
    // Викликає метод SpecifiedRequest()
    adapteeSub->SpecifiedRequest();
  }
};

// Клас Client - використовує адаптер
class Client
{
public:
  // Метод клієнта - викликає методи різних класів з ієрархії ITarget
  void ClientMethod(ITarget* target)
  {
    target->Request();
  }
};

void main(void)
{
  // 1. Передати клієнту метод SpecificRequest() класу A
  Client client; // Оголосити екземпляр класу Client

  // Оголосити екземпляр класу AdapteeSub, в якому реалізовано
  // заміщення операції Adaptee::SpecifiedRequest()
  AdapteeSub obj;
  Adapter adapter((Adaptee*)&obj); // Передати адаптеру екземпляр obj

  // Передати клієнту адаптер
  client.ClientMethod((ITarget*)&adapter); // Another operation: AdapteeSub.SpecifiedRequest()
}

 


Зв’язані теми