Породжуючі дизайн-патерни
- Абстрактна фабрика (Abstract factory)
- Будівельник (Builder)
- Фабричний метод (Factory method)
- Прототип (Prototype)
- Одинак (Singleton)
Структурні дизайн-патерни
Розглянувши породжуючі дизайн-патерни, слід приступити до наступної групи – структурних патернів.
Структурні патерни описують зв'язки між класами та формують їхню структуру.
Їх є 7:
- Адаптер;
- Міст;
- Композиція;
- Декоратор;
- Фасад;
- Легковаговик;
- Проксі.
Ці патерни є легшими для сприйняття, ніж породжуючі, тому що описують швидше не правило створення об'єктів, а логіку взаємодії між класами.
Адаптер (Adapter)
Призначення: адаптування функціональності об'єкта для використання в тій чи іншій системі.
Адаптер в даному випадку має практично те ж саме значення, що і в повсякденному житті: адаптер для розетки, адаптер для флеш-карти тощо. Тобто, якщо об'єкт не підходить для якоїсь заданої системи, то використовується адаптер, котрий стане посередником між об'єктом і системою, дозволяючи цьому об'єктові функціонувати в не призначеній для нього системі.
Для прикладу візьмемо ноутбук, який має порти для USB. Ви зраділи такому широкому функціоналу і побігли купляти флешку, щоби завантажувати на неї різні фільми та інколи використовувати для навчання. Однак, вставивши флешку в ноутбук, ви розумієте, що комп'ютер її не бачить, адже порти для USB є версією 2.0, а ваша флешка – 3.0.
В такому випадку необхідний адаптер, який дозволив би порту 2.0 читати флешку 3.0.
Для цього слід описати ці два стандарти. Наприклад, старий стандарт:
class OldStandard
{
public:
OldStandard();
~OldStandard();
virtual bool connect() = 0;
};
Та новий:
class NewStandard
{
public:
NewStandard();
~NewStandard();
virtual bool connect() = 0;
};
Тоді старий стандарт описуватиме USB 2.0:
#include "OldStandard.h"
class USB_2_0 : public OldStandard
{
public:
USB_2_0();
~USB_2_0();
virtual bool connect();
};
А новий – USB 3.0:
class USB_3_0 : public NewStandard
{
public:
USB_3_0();
~USB_3_0();
virtual bool connect();
};
Ось як виглядатиме перезавантажена функція connect()
, яка під'єднує USB 3.0:
bool USB_3_0::connect()
{
cout << "USB 3.0 connected to port." << endl;
return true;
}
На основі цих стандартів створюється адаптер, який, в залежності від версії USB, викликатиме необхідну функцію connect()
:
#include "USB_2_0.h"
#include "USB_3_0.h"
class USBAdapter : public OldStandard
{
public:
USBAdapter();
USBAdapter(NewStandard* _new);
~USBAdapter();
virtual bool connect();
private:
NewStandard* m_new;
};
USBAdapter.cpp
#include "USBAdapter.h"
USBAdapter::USBAdapter()
{
}
USBAdapter::USBAdapter(NewStandard* _new)
{
m_new = _new;
}
USBAdapter::~USBAdapter()
{
}
bool USBAdapter::connect()
{
if (m_new->connect())
return true;
else
return false;
}
Зверніть увагу, що клас USBAdapter
є похідним від OldStandard
. Адже необхідно щоб адаптер, грубо кажучи, «перетворив» новий стандарт на старий, а отже сам адаптер повинен повертати старий стандарт, коли на вхід подається новий.
Конструктором USBAdapter(NewStandard* _new)
задається об'єкт нового стандарту й адаптер перезавантажує функцію connect()
класу OldStandard
, викликаючи тим самим функцію connect()
з класу NewStandard
. Тобто, якщо в адаптер вставиться новий стандарт, то він під'єднає його як належить, хоча викликатиметься перезавантажена функція старого стандарту, яка у свою чергу викличе функцію для під'єднання нового стандарту.
Отже, як згадувалося, є ноутбук і він може приймати тільки старий стандарт:
#include "USBAdapter.h"
class Notebook
{
public:
Notebook();
~Notebook();
void plugUSB(OldStandard* oldUSB); // тільки старий формат
};
Функція plugUSB(OldStandard* oldUSB)
в якості параметра передає старий стандарт, значить безпосередньо під'єднати USB 3.0 неможливо.
void Notebook::plugUSB(OldStandard* oldUSB)
{
if (oldUSB->connect())
cout << "USB connected successfully." << endl;
else
cout << "Error. Has not connected." << endl;
}
Під'єднати USB 2.0 не проблема, а для USB 3.0 проблема вирішується адаптером.
#include "Notebook.h"
int main()
{
OldStandard* os = new USB_2_0();
Notebook notebook;
notebook.plugUSB(os); // ноутбук підтримує старий стандарт - USB 2.0
NewStandard* ns = new USB_3_0();
USBAdapter adapter(ns); // але не підтримує новий стандарт - USB 3.0
notebook.plugUSB(&adapter); // тому під'єднаємо новий стандарт через адаптер
return 0;
}
При під'єднанні USB 2.0 питань не виникає. У функцію plugUSB()
передається параметр os
, і функція під'єднала старий стандарт – oldUSB->connect()
.
Коли під'єднується USB 3.0, створюється об'єкт адаптера, в конструктор якого передається об'єкт нового стандарту – ns
. Як згадувалося, клас USBAdapter
є похідним від OldStandard
, а функція plugUSB()
саме приймає об'єкт класу OldStandard
. Тому коли передається в функції plugUSB()
викликається метод oldUSB->connect()
, то викликається перезавантажений метод з класу USBAdapter
, котрий в свою чергу викликає метод connect()
з класу NewStandard
.
USB 2.0 connected to port.
USB connected successfully.
USB 3.0 connected to port.
USB connected successfully.
Press any key to continue . . .
| Результат: |
| -------- |
| USB 2.0 connected to port. |
| USB connected successfully. |
| USB 3.0 connected to port. |
| USB connected successfully. |
| Press any key to continue . . . |
Також адаптер можна використовувати в зворотню або обидві сторони, добавивши необхідний конструктор та перевантаживши функцію.
Підсумки
Адаптер надає можливість для використання об'єкта в не призначеному для нього середовищі.
Алгоритм використання:
- Створити класи, що описують однакове призначення для об'єкта, але є при цьому різного типу та функціональності.
- Описати клас-адаптер, що наслідується, як мінімум, від одного класу, до якого необхідно адаптувати об'єкт.
- При використанні об'єкта, який не може функціонувати в заданій системі, передати його адаптеру, котрий передається в систему, адаптуючи при цьому переданий об'єкт.
Вихідний код доступний за GitHub.
Ще немає коментарів