Паттерн Bridge (Міст, Handle/Body) (реалізація схеми)
Зміст
- 1. Загальні відомості. Необхідність застосування паттерну Bridge. Рисунок
- 2. Структура паттерну. Рисунок. Учасники паттерну
- 3. Результати застосування (на основі рисунку)
- 4. Особливості реалізації паттерну
- 5. Текст програми на мові C++. Реалізація структури
- 6. Застосування паттерну Bridge
- Споріднені теми
Пошук на інших ресурсах:
1. Загальні відомості. Необхідність застосування паттерну Bridge. Рисунок
Паттерн Bridge належить до паттернів, які структурують об’єкти. Основне призначення паттерну це відділення абстракції від реалізації таким чином, щоб і абстракцію і реалізацію можна було змінювати незалежно.
Якщо потрібно реалізувати абстракцію декількома способами, то, як правило, використовують спадковість. У цьому випадку у вершині ієрархії є абстрактний клас, який містить загальний інтерфейс абстракції. З цього класу успадковуються більш конкретні класи, які забезпечують конкретну реалізацію.
Однак, такий підхід не завжди є гнучким. Причиною цього є строге прив’язування реалізації до абстракції. Як наслідок, незалежна модифікація а також можливість розширення та повторного використання є ускладненими.
Наприклад. Нехай задано абстрактний клас AbstractClass з двома конкретними реалізаціями ImpClass1, ImpClass2 як показано на рисунку 1-а. Нехай виникла необхідність для кожної конкретної реалізації додати нову абстрактну. Ця можливість забезпечується введенням абстрактного класу AbstractSubClass. Тоді для цієї можливості додатково потрібно вводити підтримку реалізацій класів ImpClass1, ImpClass2. Відповідно введені класи будуть мати імена ImpSubClass1, ImpSubClass2 (рисунок 1-b). Такий підхід є незручним і потребує повторного визначення реалізацій підкласів.
Рисунок 1. Розширення абстракції шляхом додавання класів AbstractSubClass, ImpSubClass1, ImpSubClass2
Також недоліком підходу, зображеному на рисунку 1, є те, що код клієнта стає залежним від конкретної реалізації. При створенні об’єкту ImpClass1 відбувається прив’язування абстракції до її реалізації а, отже, клієнт орієнтується саме на цей варіант реалізації. Таким чином, ускладнюється зміна реалізації для клієнта. Це призводить до необхідності перекомпіляції клієнтського коду.
⇑
2. Структура паттерну. Рисунок. Учасники паттерну
Структура паттерну Bridge зображена на рисунку 2.
Рисунок 2. Структура паттерну Bridge
Дамо короткий опис класів цієї структури які є учасниками паттерну.
- Абстрактний клас Abstraction представляє собою деяку абстракцію, яка визначає інтерфейс абстракції. У цьому класі зберігається посилання (покажчик) на об’єкт типу Implementor. Конкретні реалізації абстракцій (RefinedAbstraction) успадковуються з цього класу.
- Клас RefinedAbstraction є конкретною реалізацією абстракції, яка розширює Abstraction.
- Абстрактний клас Implementor – це інтерфейс для класів конкретних реалізацій (ConcreteImplementorA, ConcreteImplementorB). Цей клас не обов’язково повинен слідувати інтерфейсу класу Abstraction (взагалі, інтерфейси Abstraction та Implementor можуть відрізнятись). Як правило, клас Implementor визначає тільки примітивні операції. В той же час, клас Abstraction визначає операції більш високого рівня які базуються на використанні операцій класу Implementor.
- Класи ConcreteImplementorA, ConcreteImplementorB реалізують інтерфейс Implementor. Це є конкретні реалізації.
⇑
3. Результати застосування (на основі рисунку)
У літературі визначають наступні результати застосування паттерну Bridge.
- Відділення реалізації від інтерфейсу. Реалізація не має постійної прив’язки до інтерфейсу. Реалізація абстракції може конфігуруватись під час виконання програми. Тут немає залежності між класами Abstraction та Implementor яка встановлюється на етапі компіляції. Щоб змінити реалізацію, не потрібно перекомпільовувати клас Abstraction та його клієнтів.
Іншою перевагою відділення реалізації від інтерфейсу є те, що система розбивається на шари. Отже, покращується структура системи. Частини системи більш високого рівня знають тільки про класи Abstraction та Implementor.
- Підвищується ступінь розширюваності системи. Ієрархії класів Abstraction та Implementor є незалежні, тому їх можна без проблем розширювати.
- Приховування деталей реалізації від клієнтів.
⇑
4. Особливості реалізації паттерну
В залежності від того, яку структуру мають класи, можна виділити наступні основні моменти при реалізації паттерну Bridge.
- Якщо умова задачі передбачає наявність тільки одного класу Implementor, то можливо, необов’язково робити цей клас абстрактним. Це є вироджений випадок зв’язку між класами Abstraction та Implementor.
- Вибір створюваного об’єкту Implementor може бути виконаний в конструкторі класу Abstraction. Для цього конструктор повинен мати інформацію про конкретні класи, що реалізують інтерфейс Implementor. Спосіб вибору того чи іншого конкретного класу визначається умовою задачі та може бути реалізований по різному.
- При правильній побудові коду можна досягти розподілу реалізаторів. Це означає, що одна й та ж реалізація може використовуватись декількома об’єктами. Щоб це стало можливим, потрібно ввести лічильник посилань на об’єкт і в операції присвоєння посилань (operator=()) виконувати відповідну перевірку кількості посилань.
⇑
5. Текст програми на мові C++. Реалізація структури
У прикладі демонструється реалізація паттерну Bridge, структура якого зображена на рисунку 2.
// Паттерн Bridge (Міст) #include <iostream> using namespace std; // Клас, що є інтерфейсом реалізації class Implementor abstract { public: virtual void OperationImp() abstract; }; // Конкретна реалізація 1 class ConcreteImplementorA : public Implementor { public: void OperationImp() override { // тут виконується якась робота cout << "ConcreteImplementorA::OperationImp()" << endl; } }; // Конкретна реалізація 2 class ConcreteImplementorB : public Implementor { public: void OperationImp() override { // деяка робота cout << "ConcreteImplementorB::OperationImp()" << endl; } }; // Клас, що є інтерфейсом абстракції class Abstraction abstract { private: Implementor* imp = nullptr; public: // Конструктор - отримує номер реалізації Abstraction(int numImp) { // Формується вид реалізації if (numImp == 1) imp = new ConcreteImplementorA; else imp = new ConcreteImplementorB; } // Операція void Operation() { imp->OperationImp(); } // Деструктор virtual ~Abstraction() { if (imp) delete imp; } }; // Уточнена абстракція class RefinedAbstraction : public Abstraction { public: // Конструктор RefinedAbstraction(int numImp) : Abstraction(numImp) { } }; void main() { // Клієнт - містить посилання на абстракцію Abstraction* obj = nullptr; // Організація меню int numImp; cout << "Enter Implementation (1-2):" << endl; cin >> numImp; // Створити абстракцію obj = new RefinedAbstraction(numImp); // Викликати операцію obj->Operation(); if (obj) delete obj; }
⇑
6. Застосування паттерну Bridge
Паттерн Bridge застосовується у наступних випадках.
- Коли потрібно уникнути постійної прив’язки абстракції до реалізації. Прикладом цьому може бути необхідність вибору реалізації під час виконання програми.
- Коли задача передбачає розширення новими підкласами як абстракції так і реалізації. Паттерн Bridge дозволяє комбінувати різні абстракції та реалізації а також змінювати їх незалежно одна від одної.
- Зміни, які вносяться в реалізацію абстракції, не повинні впливати на клієнтів. Тобто клієнтський код не повинен перекомпільовуватись.
- Якщо необхідно повністю приховати реалізацію абстракції від клієнтів.
- У випадку, коли при внесенні змін в умову задачі, число класів абстракції починає зростати. Тут виникає необхідність розділення ієрархії на дві частини.
- Коли потрібно приховати від клієнта розділення однієї реалізації між декількома об’єктами.
⇑
Споріднені теми
- Огляд та дослідження. Приклади реалізації на C++
- Composite (Компонувальник, Дерево). Реалізація структури на C++
- Facade (Фасад). Реалізація структури на C++
⇑