You are on page 1of 84

Урок №1

Породжуючі
патерни
проєктування

Зміст
1. Вступ у патерни проєктування . . . . . . . . . . . . . . . . . . 4
1.1. Про тенденції розвитку патернів
у програмуванні. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2. Причини виникнення патернів
проєктування. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3. Поняття патерну проєктування. . . . . . . . . . . . . . . 7
1.4. Практичне застосування патернів
проєктування. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.5. Класифікація патернів. . . . . . . . . . . . . . . . . . . . . . 11
2. Короткий вступ у UML. . . . . . . . . . . . . . . . . . . . . . . . . 14
2.1. Діаграма класів. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.2. Діаграма об᾿єктів. . . . . . . . . . . . . . . . . . . . . . . . . . . 20

2
Зміст

3. Породжуючі патерни . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.1. Abstract Factory. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.2. Builder. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.3. Factory Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.4. Prototype. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
3.5. Singleton. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
3.6. Аналіз і порівняння породжуючих патернів. . . 80
4. Домашнє завдання . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
5. Використані інформаційні джерела. . . . . . . . . . . . . 83

Додаткові матеріали уроку прикріплені до даного PDF-файлу.


Для доступу до матеріалів, урок необхідно відкрити в програмі
Adobe Acrobat Reader.

3
Урок №1

1. Вступ у патерни
проєктування

Термін «патерн» (pattern) слід розуміти як «зразок».


Часто його замінюють терміном «шаблон» (template). Зі
слів Крістофера Александера1: «… будь-який патерн опи-
сує завдання, яке знову й знову виникає у нашій роботі,
і навіть принцип її вирішення, причому в такий спосіб,
що вирішення можна використовувати мільйон разів,
нічого не вигадуючи знову…» [GoF95]. Таке визначення
патерну існує в архітектурі (тобто будівництві), але воно
дуже підходить і для визначення патерну у програмуванні.
У цьому уроці ми розглянемо основні поняття, пов’я-
зані з патернами проєктування в об’єктно-орієнтовано-
му програмуванні. Також зробимо вступ в уніфіковану
мову візуального моделювання UML (Unified Modeling
Language), з використанням якої описується структура
усіх патернів проектування. Головним завданням уроку
є виклад важливої категорії патернів об’єктно-орієнто-
ваного проєктування — породжуючих патернів (Creation
Patterns), спочатку описаних у [GoF95]. Усі приклади прак-
тичного використання патернів представлені мовою C#,
їх початкові коди додаються до уроку.
Крістофер Александер — архітектор, який склав у 1970-х роках XX
1

ст. каталог патернів проєктування в галузі будівельної архітектури.


Згодом, його ідея набула широкого розвитку в галузі розробки про-
грамного забезпечення.

4
1. Вступ у патерни проєктування

1.1. Про тенденції розвитку патернів


у програмуванні
Ідея патернів проєктування спочатку виникла в ар-
хітектурі. Архітектор Крістофер Александер написав дві
революційні книги, в яких описані шаблони у будівельній
архітектурі та міському плануванні: «A Pattern Language:
Towns, Buildings, Contributions» (1977), «The Timeless Way
of Building» (1979). У цих книгах були представлені спіль-
ні ідеї, які могли використовуватися навіть в областях,
які не мають відношення до архітектури, у тому числі й
у програмуванні.
У 1987 році Кент Бек (Kent Beck) і Ворд Каннінгем (Ward
Cunningham) взяли за основу ідею Крістофера Александе-
ра і розробили шаблони розробки програмного забезпе-
чення для графічних оболонок мовою Smalltalk. У 1988 р.
Еріх Гамма (Erich Gamma) почав писати докторську дис-
ертацію при цюріхському університеті про загальну пе-
реносимість методики патернів проєктування на розроб-
ку програмного забезпечення. У 1989-1991 роках Джеймс
Коплін (James Coplien) працював над розробкою ідіом для
програмування на C++ та опублікував у 1991 році книгу
«Advanced C++ Idioms». Цього ж року, Еріх Гамма закін-
чує роботу над докторською дисертацією та переїжджає
до США, де у співпраці з Річардом Гелмом (Richard Helm),
Раль­фом Джонсоном (Ralph Johnson) і Джоном Вліссідесом
(John Vlissides) публікує книгу «Design Patterns — Elements
of Reusable Object-Oriented Software» (російськомовний
переклад книги — [GoF95]). У цій книзі описано 23 па-
терни проєктування. Також команда авторів цієї книги

5
Урок №1

відома під назвою «Банда чотирьох» (Gang of Four, ско-


рочення — GoF). Ця книга і стала причиною зростання
популярності патернів проєктування.
Таким чином, широке використання патернів у про-
грамуванні почалося з опису базових 23 шаблонів про-
єктування «Банди чотирьох».
Наступним кроком став опис Мартіна Фаулера «Enter­
prise Patterns», де розкриваються типові рішення при роз-
робці корпоративних додатків, наприклад, робота з ба-
зами даних, транзакціями і т.п.
Джошуа Керієвські показав, як можна постійним ре-
факторингом1, керуючись базовими принципами ООП,
забезпечити еволюцію коду, переміщаючи його від одно-
го патерну до іншого залежно від вимог.
Після початку акцентування уваги на модульному тес-
туванні (Unit Testing) з’явилося поняття тестування про-
грамного коду. Усі патерни були переосмислені з позицій
тестування. При цьому, наприклад, виявилося, що патерн
Sing­leton — це антипатерн (див. пункт 3.5), а Abstract Fac­tory
(див. пункт 3.1) взагалі замінили IoC (Inversion of Control).
Після виходу книги «xUnit Test Patterns» у 2008 році з’яви-
лися декілька десятків патернів тестування.

1.2. Причини виникнення патернів проєктування


Наприкінці 80-х років XX століття у сфері розробки
програмного забезпечення, зокрема, об’єктно-орієнтова-
ному проєктуванні, накопичилося багато різних і схожих
Рефакторинг  — зміни внутрішнього пристрою програмного коду
1

без модифікації зовнішньої функціональності.

6
1. Вступ у патерни проєктування

за своєю сутністю, рішень. Ці рішення вимагали система-


тизації, узагальнення на всілякі ситуації, і навіть доступ-
ного описання, що сприяло розумінню їх спеціалістами,
які раніше їх використовували. Таке впорядкування знань
в об’єктно-орієнтованому проєктуванні дозволило пов-
торно використовувати готові і вже перевірені рішення,
а не знову і знову «винаходити велосипед».
Вирішення проблеми систематизації накопиченого
досвіду в об’єктно-орієнтованому проєктуванні, а також
представлення його широкому колу розробників, які взя-
ли на себе патерни проєктування.

1.3. Поняття патерну проєктування


У загальному сенсі, патерн є зразком вирішення пев-
ної задачі. Тому, це рішення можна використовувати у різ-
них ситуаціях. В об᾿єктно-орієнтованому проєктуванні
патерну можна надати такі визначення.
Під патерном проєктування (design pattern) розу-
мітимемо опис взаємодії об᾿єктів і класів, адап-
тованих для вирішення спільного завдання про-
єктування у конкретному контексті [GoF95].
Алгоритми процедурного програмування також є па-
тернами, але не проєктування, а обчислень. Оскільки вони
вирішують не архітектурні, а обчислювальні завдання.
Також слід підкреслити відмінність патернів проєк-
тування від ідіом. Ідіоми — це патерни, що описують
типові рішення конкретною мовою програмування, а
патерни проєктування не залежать від вибору мови

7
Урок №1

(хоча їх реалізації, найчастіше, залежні від мови про-


грамування).
Загалом, кожен патерн складається з таких складових:
1. Назва є унікальним ідентифікатором патерну. По-
силання на назву дає можливість описати завдання
проєктування і підхід до її вирішення. Назви патер-
нів проєктування, описаних у [GoF95], є загально-
прийнятими. Тому, наприклад, можуть використо-
вуватися у проєктній документації як посилання на
типові архітектурні рішення.
2. Завдання визначає ситуацію, у якій можна використо-
вувати патерн. При формулюванні завдання патерна
важливо описати, у якому контексті вона виникає.
3. Рішення завдання проєктування у вигляді патерну
визначає загальні функції кожного елемента дизайну
і відносини між ними. При цьому слід підкреслити,
що розглядається не конкретна, а загальна ситуація,
оскільки патерн проєктування — це шаблон, який
може застосовуватися багаторазово для вирішення
типового завдання у різних контекстах.
4. Результати представляють наслідки застосування па-
терну. Вони описують переваги і недоліки обраного
рішення, його наслідки, різні компроміси і варіації
патерна. Також, у результатах слід згадати особли-
вості використання патерна в контексті конкретної
мови програмування.
Патерн — це загальний опис хорошого способу вирі-
шення задачі. Поряд із поняттям «патерн» часто фігурує

8
1. Вступ у патерни проєктування

поняття «антипатерн», або «антишаблон». Антипатерн —


це неправильне, часто повторюване, рішення, яке не реко-
мендується використовувати. Мета патерну — розпізнати
можливість застосування вірного вирішення проблеми.
А мета антипатерну — виявити неправильну ситуацію і
запропонувати підхід до її усунення.

1.4. Практичне застосування патернів проєктування


Патерни проєктування становлять загальні рішення
типових завдань об’єктно-орієнтованого проєктування.
При цьому, може скластися враження, що для форму-
вання вдалого дизайну системи в першу чергу необхід-
но спроєктувати її частини. Виходячи з того чи іншого
патерну проєктування, інакше кажучи, необхідно під-
вести дизайн системи під відомі зразки. З практичної
точки зору, відштовхуватися від патернів проєктування
при розробці дизайну системи не є найефективнішим і
гнучким рішенням. Найчастіше, такий підхід може при-
звести до правильного рішення, з академічного погля-
ду, але не відповідає вимогам гнучкості та масштабова-
ності рішення.
При розробці дизайну системи слід керуватися таки-
ми основними принципами:
■ Завжди формувати простий дизайн: із двох запро-
понованих рішень, як правило, найкращим є те, що
простіше.
■ Слабка залежність: дизайн модуля повинен бути та-
ким, щоб у разі його модифікації залежні фрагменти
системи не вимагали, або майже не вимагали, змін.

9
Урок №1

«А де ж тоді патерни проєктування?» — спитаєте ви.


При такому підході до проєктування логічної струк-
тури системи можна побачити типові завдання, які вже
вирішені за допомогою патернів проєктування. Тоді до-
цільно застосувати готове шаблонне рішення, оцінивши
при цьому можливості і перспективи на підставі його опи-
су. Але тут знову не слід забувати про принципи просто-
го дизайну та слабкої залежності, тому що зайве бажан-
ня скористатися патерном проєктування може сприяти
формуванню поганого дизайну. Варто також пам’ятати,
що дизайн системи постійно еволюціонує, тому успішно
застосований патерн з часом може трансформуватися в
зовсім інший або зовсім зникнути.
Маємо відзначити, що завдяки патернам проєкту-
вання відбулася уніфікація термінології: посилання на
них як на комплексні рішення задач дизайну можна
додавати у проєктну документацію і використовувати
в дискусіях.
Практичний досвід розробки програмного забезпе-
чення говорить, що будь-який результат завжди підля-
гає ретельній перевірці. Одним із індикаторів поганого
дизайну системи можуть бути наступні негативні явища
у програмному коді (code smell):
■ Дубляж коду: однаковий (або майже однаковий код)
у різних частинах проєкту.
■ Довгі методи: підпрограми з великою кількістю ко-
дових ліній.
■ Великі класи: часто, на один клас припадає дуже ба-
гато різнорідних функцій.

10
1. Вступ у патерни проєктування

■ Заздрість: клас надмірно використовує методи іншого


класу.
■ Порушення приватності: клас надмірно залежить від
деталей реалізації іншого класу.
■ Порушення наслідування: клас перевизначає спосіб
базового класу і навіть порушує його договір (тобто,
первісне призначення).
■ Лінивий клас: клас, який робить замало, тобто клас
із недостатньою чи нецілісною функціональністю.
■ Надмірна складність: примусове використання
складних рішень, в яких простий дизайн був би
достатнім.
■ Надмірно довгі ідентифікатори: у тому числі вико-
ристання довгих назв для позначення залежностей,
які мають бути неявними.
При практичному застосуванні патернів проєктуван-
ня також не слід забувати і те, що ООП постійно розви-
вається. Підходи, які раніше приймалися за зразок, зго-
дом можуть стати антипатернами, а їм на зміну можуть
прийти значно кращі рішення (як, наприклад, сталося з
патерном Singleton (див. пункт 3.5)).

1.5. Класифікація патернів


В об᾿єктно-орієнтованому аналізі та проєктуванні
(ООП) на сьогоднішній день розроблено багато різних
патернів, і патерни проєктування (зокрема, породжую-
чі патерни), що розглядаються у цьому уроці — це лише
одна з підкатегорій.

11
Урок №1

Для повноти картини коротко розглянемо класифі-


кацію усіх патернів ООАП:
1. Архітектурні патерни. Описують фундаментальні
методи структурування програмних систем. Ці па-
терни відносяться до рівня систем та підсистем, а не
класів. Патерни цієї категорії систематизував та опи-
сав К. Ларман.
2. Патерни проєктування. Описують структуру про-
грамних систем у термінах класів. Найбільш відоми-
ми в цій галузі є 23 патерни, описані в [GoF95].
3. Патерни аналізу. Подають загальні схеми організа-
ції процесу об᾿єктно-орієнтованого моделювання.
4. Патерни тестування. Визначають загальні схеми
організації процесу тестування програмних систем.
5. Патерни реалізації. Описують шаблони, які вико-
ристовуються для написання програмного коду.
Більш детально розглянемо патерни проєктування,
узагальнивши класифікації, представлені в [Grand2004],
[DPWiki], [DPOverview]:
1. Основні патерни (Fundamental Patterns). Представ-
ляють найважливіші фундаментальні патерни, які
активно використовуються іншими патернами.
2. Породжуючі патерни (Creational Patterns). Визнача-
ють способи створення об᾿єктів у системі.
3. Структурні патерни (Structural Patterns). Описують ме-
тоди побудови складних структур із класів та об᾿єктів.
4. Поведінкові патерни (Behavioral Patterns). Описують
методи взаємодії між об᾿єктами.

12
1. Вступ у патерни проєктування

5. Патерни паралельного програмування (Concurrency


Patterns). Призначені для координування паралель-
них операцій; вирішують такі проблеми: боротьба за
ресурси і взаємоблокування.
6. Патерни MVC. Містить такі патерни як MVC (Model —
View — Controller), MVP (Model — View — Presen­ter)
та ін.
7. Патерни корпоративних систем (Enterprise Patterns).
Надають рішення для побудови та інтеграції великих
корпоративних програмних систем.
У праці [GoF95] представлено 23 патерни проєкту-
вання. За своїм призначенням ці патерни класифіку-
ються так:
1. Породжуючі патерни (Creational Patterns). Абстрагу-
ють процес інстанціювання; роблять систему незалеж-
ною від того, як у ній створюються, компонуються і
представляються об᾿єкти.
2. Структурні патерни (Structural Patterns). Вирішують
питання про створення з класів та об’єктів великих
структур.
3. Поведінкові патерни (Behavioral Patterns). Розподі-
ляють обов᾿язки між об᾿єктами; описують методи
їх взаємодії.
В поданому навчальному курсі ми, головним чином,
вивчатимемо патерни з [GoF95]. Зокрема, у поточному
уроці ми детально розглянемо породжуючі патерни.

13
Урок №1

2. Короткий вступ у UML

Уніфікована мова візуального моделювання (Unified


Modeling Language — UML) являє собою засіб для візуаль-
ного уявлення елементів програмної системи Оскільки
в процесі вивчення патернів проєктування, розглянуті
об’єктно-орієнтовані моделі зручно представляти гра-
фічно у вигляді діаграм класів UML, тому нам важливо
розглянути їх основні елементи.

2.1. Діаграма класів


Діаграма класів призначена для уявлення статич-
ної структури системи у термінах класів об’ектно-
орієнтованого програмування. У нашому навчально-
му курсі всі патерни проєктування, а також приклади
їх використання, описуються з використанням діаграм
цього типу.
Одним із основних складових діаграми класів є власне
клас. Графічно, він зображується прямокутником, розді-
леним на три частини (рис. 2.1.1):

Рисунок 2.1.1. Графічне зображення класу

14
2. Короткий вступ у UML

1) назва класу;
2) атрибути (поля, члени даних) класу;
3) операції (методи, функції-члени) класу.
Секції 2 і 3 у зображенні класу можуть бути відсутніми.
Загалом, атрибут класу позначається так
[visibility] name [multiplicity] [: type]
[= initial value] [{property}]

де visibility — область видимості, яка може отримува-


ти такі значення: (+) — Public, (#) — Protected, (–) —
Private;
name — назва атрибута (єдиний обов᾿язковий па­
ра­метр);
multiplicity — кратність, тобто кількість примірників
атрибуту;
type — тип, до якого належить атрибут;
initial value — початкове значення;
property — інші властивості, наприклад, readonly.
Повна форма визначення операції класу має такий
вигляд:
[visibility] name ([parameter list]) [: return type]
[{property}]

де visibility — область видимості, яка може отримува-


ти такі значення: (+) — Public, (#) — Protected, (–) —
Private;
name — назва операції;

15
Урок №1

parameter list — список параметрів, кожен елемент


якого позначається так:
[direct] name : type [= default value]

direct — напрямок передачі значення: in — вхідні,


out — вихідні, inout — вхідні та вихідні одночасно;
name — назва параметра;
type — тип параметра;
default value — значення за замовчуванням;
return type — тип поверненого значення;
property — інші властивості операції.
Якщо клас чи операція класу визначені як абстрактні,
їх назва позначається курсивом. Назви статичних класів
та статичних членів класу помічаються підкресленням.
Інтерфейси на діаграмі класів відображаються дво-
ма способами: повноформатно, як клас зі стереотипом
Inter­face (рис. 2.1.2), і у скороченій круговій нотації
(рис. 2.1.3).

Рисунок 2.1.2. Повноформатна нотація інтерфейсу

Рисунок 2.1.3. Кругова нотація інтерфейсу

16
2. Короткий вступ у UML

Найважливішим для розуміння аспектом у цьому ви-


падку є зв᾿язки між класами:
1. Зв’язок наслідування (Generalization Relationship). По-
дібний до успадкування класів в об᾿єктно-орієнтова-
ному програмуванні. Позначається лінією з трикут-
никовою стрілкою, стрілка спрямована від підкласу
до суперкласу (рис. 2.1.4).

Рисунок 2.1.4. Зв᾿язок успадкування на діаграмі класів

2. Зв’язок реалізації (Realization Relationship). Являє со-


бою окремий випадок зв᾿язка успадкування.

Рисунок 2.1.5. Зв᾿язок реалізації на діаграмі класів

17
Урок №1

Використовується для відображення реалізації кла-


су інтерфейсу. Графічно зображується пунктирною
лінією з трикутником на кінці; трикутник вказує на
інтерфейс (рис. 2.1.5).
3. Зв’язок залежності (Dependency Relationship). Позна-
чає факт використання заданим класом іншого класу.
Зв᾿язок залежності може, наприклад, застосовується
у таких випадках:
■ клас містить локальну змінну, що належить до ін-
шого класу;
■ клас використовує об’єкт іншого класу як параметр
операції;
■ метод класу повертає об’єкт іншого класу;
■ клас використовує статичну операцію іншого класу.
Графічно відношення залежності позначається пунк-
тирною стрілкою, спрямованою від класу, який використо-
вує до класу, який використовується (рис. 2.1.6).

Клас А використовує клас В

Рисунок 2.1.6. Відношення залежності


на діаграмі класів

4. Зв’язок асоціації (Association Relationship). Позначає


певне структурне ставлення між класами. Виникає

18
2. Короткий вступ у UML

між класами, наприклад, коли в одному класі в якості


поля міститься об᾿єкт, що належить до іншого кла-
су. Графічно відображається у вигляді суцільної лінії
(рис. 2.1.7).

Рисунок 2.1.7. Зв᾿язок асоціації


на діаграмі класів

5. Зв’язок агрегації (Aggregation Relationship). Пред-


ставлений складнішою формою зв᾿язка асоціації, яка
слугує позначенням логічного включення для форму-
вання цілого з елементів. Графічно помічається у ви-
гляді суцільної лінії з ромбом поряд із класом, який
позначає ціле (рис. 2.1.8).

Рисунок 2.1.8. Зв᾿язок агрегації


на діаграмі класів

19
Урок №1

6. Зв’язок композиції (Composition Relationship). Має


складніший вид зв᾿язка агрегації. Передбачає вклю-
чення частини до цілого, при цьому частина не може
існувати окремо від цілого. Графічно зображується як
агрегація, але з чорним ромбом (рис. 2.1.9).

Інтегрована відеоплата не може існувати


окремо від материнської плати

Рисунок 2.1.9. Зв᾿язок композиції на діаграмі класів

2.2. Діаграма об᾿єктів


На відміну від діаграми класів, що відображає статич-
ну структурну модель системи, діаграма об᾿єктів пред-
ставляє екземпляри класів та відносини між ними у пев-
ний момент роботи програми. Інакше кажучи, діаграма
об᾿єктів — це знімок стану працюючої системи у певний
момент часу.
Загалом, назва об᾿єкта на діаграмі позначається під-
кресленням і має вигляд:
Назва об᾿єкту: Назва класу

При цьому, одна зі складових назв може не вка­зу­


ватися.
Найпростіший приклад діаграми об᾿єктів зображе-
но на рисунку 2.2.1.

20
2. Короткий вступ у UML

Рисунок 2.2.1. Приклад діаграми об᾿єктів

Представлені вище діаграми класів та об᾿єктів будуть


використовуватися нами у процесі вивчення усіх патер-
нів проєктування. Усі приклади цього курсу написані на
С++. До кожного уроку прикріплена папка з прикладами
до пройденого матеріалу під назвою Samples.

21
Урок №1

3. Породжуючі патерни
Породжуючі патерни проєктування абстрагують
процес інстанціювання. Вони допомагають зробити си-
стему незалежною від способу створення, композиції та
уявлення об’єктів. Патерни такої категорії дозволяють
відповісти на запитання: хто, коли і як створює об’єкти
в системі. [GoF95]
3.1. Abstract Factory
3.1.1. Назва патерну
Abstract Factory / Абстрактна фабрика
Також відомий під назвою: Toolkit / Інструментарій
Описаний у праці [GoF95].
3.1.2. Мета патерну
Надає інтерфейс створення сімейства взаємопов᾿я-
заних і взаємозалежних об᾿єктів, не специфікуючи кон-
кретні класи, об᾿єкти яких створюватимуться. [GoF95]
3.1.3. Патерн слід використовувати коли:
■ система має залежати від того, як у ній створюються
і компонуються об’єкти;
■ об’єкти сімейства мають використовуватися разом;
■ система має конфігуруватися одним із сімейств об’єктів;
■ потрібно надати інтерфейс бібліотеки, не розкриваючи
її внутрішньої реалізації.

22
3. Породжуючі патерни

3.1.4. Причини виникнення патерну


Перед нами виникає завдання — розробити гру «Су-
пер Ралі», суть якої, звичайно ж, полягає у автоперего-
нах. Одною з основних функціональних вимог полягає
в тому, що гравець повинен мати можливість вибирати
собі автомобіль для участі у перегонах. Кожен із запро-
понованих ігрових автомобілів складається зі спеціаль-
ного набору складових (двигуна, коліс, кузова, коробки
передач, бензобака), які в сукупності визначають мож-
ливості автомобіля (швидкість, маневреність, стійкість
до пошкоджень, тривалість безперервної гонки без пере­
заправки та ін.).
Оскільки у грі може бути багато різних типів авто-
мобілів, навіть їх кількість може змінюватися динамічно
(наприклад, залежно від досвіду гравця), то, з технічного
погляду, клієнтський код, що реалізує конфігурування
автомобіля спеціальним сімейством складових, не пови-
нен залежати від типу обраного авто.
Для реалізації цієї вимоги пропонується наступне.
Розглянемо інтерфейс «ФабрикаСкладових», призначен-
ня якого — уявити інтерфейс для конкретних класів-фа-
брик, які створюватимуть сімейства складових для кож-
ного конкретного типу автомобіля.
Методи цього класу мають повертати посилання на
абстрактні складові, що дозволить у конкретних кла-
сах-фабриках створювати конкретні складові (підкласи
абстрактних складових). Такий підхід продемонстрова-
но на діаграмі класів на рисунку 3.1.1.

23
Урок №1

Абстрактні складові

interface, АбстрактнийДвигун
ФабрикаСкладових

АбстрактнеКолесо
+ СтворитиДвигун() : АбстрактнийДвигун
+ СтворитиКолесо() : АбстрактнеКолесо
+ СтворитиКузов() : АбстрактнийКузов АбстрактнийКузов
+ СтворитиКоробкуПередач() : Абстрактна-
КоробкаПередач
АбстрактнаКоробкаПередач
+ СтворитиБензобак() : АбстрактнийБензобак

АбстрактнийБензобак

Тюнер Автомобіль

Фабрика Тюнер ТюнерДвигун

+ СтворитиДвигун() : АбстрактнийДвигун
ТюнерКолесо
+ СтворитиКолесо() : АбстрактнеКолесо
+ СтворитиКузов() : АбстрактнийКузов
+ СтворитиКоробкуПередач() : Абстрактна- ТюнерКузов
КоробкаПередач
+ СтворитиБензобак() : АбстрактнийБензобак
ТюнерКоробкаПередач

ТюнерБензобак

Экзотик-автомобиль

ФабрикаЭкзотик ЕкзотикДвигун

+ СтворитиДвигун() : АбстрактнийДвигун
ЕкзотикКолесо
+ СтворитиКолесо() : АбстрактнеКолесо
+ СтворитиКузов() : АбстрактнийКузов
+ СтворитиКоробкуПередач() : Абстрактна- ЕкзотикКузов
КоробкаПередач
+ СтворитиБензобак() : АбстрактнийБензобак
ЕкзотикКоробкаПередач

ЕкзотикБензобак

Рисунок 3.1.1. Фабрики складових ігрових автомобілів

24
3. Породжуючі патерни

Клієнтський код, який «збирає» автомобіль з деталей,


конфігурується інтерфейсним посиланням «Фабрика­
Складових», методи якого повертають посилання на аб-
страктні складові. Таким чином, ми маємо можливість
передавати клієнту об᾿єкт конкретної фабрики, що ство-
рює сімейство об᾿єктів конкретних складових.
Маємо відзначити суттєвий недолік продемонстрова-
ного підходу: визначення інтерфейсу «Фабрика­Складових»
на етапі компіляції передбачає створення фіксованого
набору продуктів, з чого виходить:
1) кожна з конкретних фабрик має створювати кон-
кретні продукти, відповідні цьому набору, тобто кіль-
кість і тип продуктів жорстко визначається на етапі
компіляції і не може змінюватися на етапі виконання;
2) за необхідності додавання нового типу товарів
потрібно змінювати абстрактну фабрику і кожну з
конкретних фабрик.
Щоб уникнути таких проблем як альтернативу аб-
страктній фабриці, можна розглядати патерн прототип
(див. пункт 3.4). Також слід зазначити, що при реалізації
цього підходу кожну з конкретних фабрик можна визна-
чити як Singleton (див. пункт 3.5), оскільки у програмі,
найімовірніше, не буде потреби створювати більше од-
ного екземпляра конкретної фабрики.

3.1.5. Структура патерну


Загальна структура патерну Abstract Factory пред-
ставлена на рисунку 3.1.2.

25
Урок №1

Рисунок 3.1.2. Загальна структура патерну


Abstract Factory

Учасники патерну:
■ AbstractFactory — абстрактна фабрика. Представлений
загальним інтерфейсом створення сімейства продуктів.
■ ConcreteFactory — конкретна фабрика. Реалізує інтер-
фейс AbstractFactory і створює сімейство конкретних
продуктів.
■ AbstractProduct — абстрактний продукт. Представле-
ний інтерфейсом абстрактного продукту, посилання
на який повертають методи фабрик.
■ ConcreteProduct — конкретний продукт. Реалізує кон-
кретний тип продукту, який створюється конкретною
фабрикою.
Зв’язки між учасниками:
■ Клієнт знає лише про існування абстрактної фабрики
і абстрактних продуктів.

26
3. Породжуючі патерни

■ Для створення сімейства конкретних продуктів, клі-


єнт змінюється відповідним екземпляром конкретної
фабрики.
■ Методи конкретної фабрики створюють екземпляри
конкретних продуктів, повертаючи їх посиланнями
на відповідні абстрактні продукти.
3.1.6. Результати використання партерну
Дозволяє ізолювати конкретні класи продуктів. Клі-
єнт знає лише про існування абстрактних продуктів, що
веде до спрощення його архітектури.
Спрощує заміну сімейств продуктів. Для використан-
ня іншого сімейства товарів достатньо конфігурувати клі-
єнтський код, відповідний до конкретної фабрики. Дає
гарантію у поєднані продуктів.
Оскільки кожна конкретна фабрика створює гру-
пу товарів, вона стежить за забезпеченням їх сполучу-
ваності.
Вагомим недоліком патерну є складність підтримки
нового виду товарів. Для додавання нового продукту
потрібно змінювати всю ієрархію фабрик, і навіть клі-
єнтський код.
3.1.7. Практичний приклад використання патерну
Нехай постає завдання розробити програмне забез-
печення для магазину комп᾿ютерної техніки. На думку
замовника, однією з найбільш популярних можливостей
програми буде можливість швидкого створення конфі-
гурації системного блоку.

27
Урок №1

Для спрощення викладу припустимо, що до складу


конфігурації системного блоку входять:
1) бокс (Box);
2) процесор (Processor);
3) системна плата (MainBoard);
4) жорсткий диск (Hdd);
5) оперативна пам’ять (Memory).
Для кожної із цих складових визначимо абстрактний
клас. Конкретні моделі складових визначатимемо шляхом
успадкування від абстрактного базового класу (одразу
слід зазначити, що такий підхід не найкращий із прак-
тичної точки зору).
Клас, що представляє конфігурацію системного бло-
ку, назвемо Pc (лістинг 3.1.1).
Лістинг 3.1.1. Клас Pc

// клас з описанням комп’ютера та його складових


class PC {
Box* box;
Processor* processor;
MainBoard* mainBoard;
Hdd* hdd;
Memory* memory;
public:
PC() {
box = NULL;
processor = NULL;
mainBoard = NULL;
hdd = NULL;
memory = NULL;
}

28
3. Породжуючі патерни

virtual ~PC() {
if (box)
delete box;

if (processor)
delete processor;

if (mainBoard)
delete mainBoard;

if (hdd)
delete hdd;

if (memory)
delete memory;
}
Box* GetBox() {
return box;
}
void SetBox(Box* pBox){
box = pBox;
}

Processor* GetProcessor() {
return processor;
}
void SetProcessor(Processor* pProcessor) {
processor = pProcessor;
}

MainBoard* GetMainBoard() {
return mainBoard;
}
void SetMainBoard(MainBoard* pMainBoard) {
mainBoard = pMainBoard;
}

29
Урок №1

Hdd* GetHdd() {
return hdd;
}
void SetHdd(Hdd* pHdd) {
hdd = pHdd;
}

Memory* GetMemory() {
return memory;
}
void SetMemory(Memory* pMemory) {
memory = pMemory;
}
};

Припустимо, що наша програма має створювати шабло-


ни типових конфігурацій двох типів: домашня та офісна.
З логічного погляду важливо, щоб усі складові зада-
ної конфігурації системного блоку працювали злагодже-
но, тому відповідальність за їх створення треба покласти
на один клас-фабрику. Ця фабрика має реалізувати ін-
терфейс IPcFactory (лістинг 3.1.2). Зауважимо, що мето-
ди його інтерфейсу повертають посилання на класи аб-
страктних продуктів.
Лістинг 3.1.2. Інтерфейс IPcFactory

/*
* Інтерфейс фабрики по створенню конфігурації
* системного блоку персонального комп’ютера
*/
class IPCFactory {
public:
virtual Box* CreateBox() = 0;

30
3. Породжуючі патерни

virtual Processor* CreateProcessor() = 0;


virtual MainBoard* CreateMainBoard() = 0;
virtual Hdd* CreateHdd() = 0;
virtual Memory* CreateMemory() = 0;
};

Для створення наборів компонентів типових конфігу-


рацій визначимо класи конкретних фабрик: HomePcFactory
(лістинг 3.1.3) і OfficePcFactory (лістинг 3.1.4). У кожному
з create-методів цих класів створюється об’єкт конкрет-
ного класу продукту відповідного типу конфігурації.
Лістинг 3.1.3. Клас HomePcFactory

/*
* Фабрика по створенню "домашньої" конфігурації
* системного блоку персонального комп’ютера
*/
class HomePcFactory : public IPCFactory {
public:
Box* CreateBox()
{
return new SilverBox();
}

Processor* CreateProcessor()
{
return new IntelProcessor();
}

MainBoard* CreateMainBoard()
{
return new MSIMainBord();
}

31
Урок №1

Hdd* CreateHdd()
{
return new SamsungHDD();
}

Memory* CreateMemory()
{
return new Ddr2Memory();
}
};

Лістинг 3.1.4. Клас OfficePcFactory

/*
* Фабрика по створенню "офісної" конфігурації
* системного блоку персонального комп’ютера
*/
class OfficePcFactory : public IPCFactory {

public:
Box* CreateBox()
{
return new BlackBox();
}

Processor* CreateProcessor()
{
return new AmdProcessor();
}

MainBoard* CreateMainBoard()
{
return new AsusMainBord();
}

32
3. Породжуючі патерни

Hdd* CreateHdd()
{
return new LGHDD();
}

Memory* CreateMemory()
{
return new DdrMemory();
}
};

Визначимо клас PcConfigurator, який відповідає за


конфігурування об’єтку типу Pc вибраним сімейством
складових (лістинг 3.1.5).
Лістинг 3.1.5. Клас PcConfigurator

// клас конфігуратор
class PcConfigurator
{
/*
* Фабрика складових персонального комп’ютера
*/

IPCFactory* pcFactory;

public:
PcConfigurator() {
pcFactory = NULL;
}

virtual ~PcConfigurator() {
if(pcFactory)
delete pcFactory;
}

33
Урок №1

IPCFactory* GetPCFactory() {
return pcFactory;
}

void SetPCFactory(IPCFactory* pcCurrentFactory) {


pcFactory = pcCurrentFactory;
}

/*
* Метод конфігурування системного блоку
*/
void Configure(PC* pc)
{
pc->SetBox(pcFactory->CreateBox());
pc->SetMainBoard(pcFactory->CreateMainBoard());
pc->SetHdd(pcFactory->CreateHdd());
pc->SetMemory(pcFactory->CreateMemory());
pc->SetProcessor(pcFactory->CreateProcessor());
}
};

Клас PcConfigurator приймає екземпляр конкретної


фабрики і за допомогою її методів створює складові пер-
сонального комп’ютера. При цьому слід наголосити, що
PcConfigurator працює з інтерфейсним посиланням IPc­
Factory, тобто він нічого не знає про конкретні фабрики
конфігурацій і конкретні складові. У цьому і проявляєть-
ся вся суть абстрактної фабрики — конкретну фабрику
можна поділити на етапі виконання програми, при цьо-
му клієнтський код (в даному випадку PcConfigurator) не
залежить від конкретних фабрик чи продуктів.
Повна діаграма класів представленої реалізації зо-
бражена на рисунку 3.1.3.

34
3. Породжуючі патерни

Рисунок 3.1.3. Модель класів конфігуратора


системного блоку персонального комп᾿ютера

У наведеній реалізації легко побачити типовий недо-


лік абстрактної фабрики: під час зміни кількості продук-
тів необхідно модифікувати всю ієрархію фабрик, а також
клієнтський код.

35
Урок №1

Ще один підхід (можливо, більш гнучкий) до реа-


лізації завдання типових конфігурацій персонально-
го комп᾿ютера представлений у п. 3.4. Він базується на
використанні патерну Prototype. Повний код прикладу
міститься в папці з прикладами до цього уроку. Назва
проєкту Abstract­Factory.
3.2. Builder
3.2.1. Назва патерну
Builder/Будівник.
Описаний у праці [GoF95].
3.2.2. Мета патерну
Відокремлює процес конструювання складного об᾿єк-
та від його уявлення так, що в результаті одного й того ж
процесу конструювання виходять різні уявлення [GoF95].
Інакше кажучи, клієнтський код може створювати склад-
ний об᾿єкт, визначаючи для нього не тільки тип, а й вміст.
При цьому клієнт не повинен знати деталі конструюван-
ня об᾿єкта [Grand2004].
3.2.3. Патерн слід використовувати коли:
■ Загальний алгоритм побудови складного об᾿єкта не
повинен залежати від специфіки кожного з його етапів.
■ В результаті того самого алгоритму конструювання
треба отримати різні продукти.
3.2.4. Причини виникнення патерну
Уявімо, що ми маємо конвеєр для випуску автомобі-
лів. Суть конвеєра полягає у поетапній побудові склад-

36
3. Породжуючі патерни

ного продукту, яким у цьому випадку є автомобіль. Кон-


веєр визначає загальну послідовність кроків (тобто ал-
горитм) конструювання. При цьому, специфіка кожного
з кроків визначається, головним чином, моделлю авто-
мобіля, що збирається. Такий розподіл загального ал-
горитму побудови та специфіки кожного з етапів доз-
волять компанії значно заощадити: на одному конвеєрі
можуть випускатися автомобілі різних моделей з різни-
ми характеристиками.
Із загально-технологічного погляду, автомобіль на
конвеєрі проходить такі етапи:
1. Складання кузова.
2. Встановлення двигуна.
3. Встановлення коліс.
4. Фарбування.
5. Підготовка салону.
Технічні деталі процесів, що відбуваються на кожному
кроці, вже відомі певній технології виробництва моделі
автомобіля. Нехай завод може виробляти автомобілі та-
ких моделей: автомобілі класу «міні», спортивні автомо-
білі, позашляховики. За таким розподілом виробничого
процесу для компанії не становитиме проблем доповни-
ти спектр моделей, що випускаються за новими зразками
без змін загального конвеєрного циклу.
Перенесемося тепер у світ об’єктно-орієнтованого
програмування.
Визначимо клас «Конвеєр», який буде прототипом
реального конвеєра, тобто визначатиме загальну послі-

37
Урок №1

довність кроків конструювання. Метод «Зібрати» цьо-


го класу виконуватиме процес конструювання у вигляді
виконання цих етапів незалежно від технічних деталей
реалізації кожного етапу.
Відповідальність за реалізацію етапів конструю-
вання покладемо на абстрактний клас, який назвемо
«Тех­но­логія­Моделі». Внаслідок застосування конкрет-
них підкласів класу «ТехнологіяМоделі» ми отримуємо
різні моделі автомобілів, тобто екземпляри різних кла-
сів автомобілів. У нашому випадку визначимо такі під-
класи класу «ТехнологіяМоделі»: «ТехнологіяМініАвто»,
«Технологія­Спортивний­Авто», «ТехнологіяПозашляхо-
вик». Кожна з цих технологій передбачає випуск відпо-
відних моделей авто: «МініАвто», «СпортивнийАвто»,
«Поза­шля­ховик».
Для початку виробництва автомобіля необхідно за-
дати конкретну технологію для конвеєра і викликати ме-
тод «Зібрати». Після завершення процесу складання, го-
товий автомобіль можна отримати у об’єкта технології
за допомогою методу «ОтриматиРезультат()».
Діаграма класів описаної об’єктно-орієнтованої мо-
делі представлена на рисунку 3.2.1.
Побудована модель має низку переваг:
1) певна технологія конструювання будується за
загальним шаблоном під час реалізації дій, які він
визначає;
2) загальний алгоритм процесу конструювання не
залежить від деталей, специфічних для конкретної
технології;

38
3. Породжуючі патерни

Конвеєр

- Технологія: ТехнологіяМоделі
ТехнологіяМоделі
+ Зібрати
технологія.ЗібратиКузов(); + ПідготуватиСалон()
технологія.ВстановитиДвигун(); + Пофарбувати()
for(i=1; i<=4; ++i){ + ЗібратиКузов()
технологія.ВстановитиКолесо(); + ВстановитиДвигун()
} + ВстановитиКолесо()
технологія.Пофарбувати;
технологія.ПідготуватиСалон();

ТехнологіяМініАвто ТехнологіяСпортивнеАвто ТехнологіяПозашляховик

- авто: МиниАвто - авто: МиниАвто авто: Позашляховик

+ ЗібратиКузов() + ЗібратиКузов() +Зібрати Кузов,


+ ВстановитиДвигун() + ВстановитиДвигун() +Встановити Двигун,
+ ВстановитиКолесо() + ВстановитиКолесо() +Встановити Колесо,
+ Пофарбувати() + Пофарбувати() +Пофарбувати,
+ ПідготуватиСалон() + ПідготуватиСалон() +Підготувати Салон,
+ Отримати результат(): + Отримати результат(): +Отримати результат:
МініАвто МініАвто Позашляховик

МініАвто СпортивнеАвто Позашляховик

Рисунок 3.2.1. Діаграма класів моделі конвеєра


з виробництва автомобілів

3) є можливість безпеки зростання складності струк-


тури моделі реалізувати під загальний алгоритм значну
кількість певних технологій.

3.2.5. Структура патерну


У загальному випадку, структура патерна Builder має
вигляд, представлений на рисунку 3.2.2.

39
Урок №1

Рисунок 3.2.2. Структура патерну Builder

Учасники патерну:
■ Builder (ТехнологіяМоделі) — будівельник
▷ Забезпечує інтерфейс для поетапного конструю-
вання складного об᾿єкта (продукту) з елементів.
■ ConcreteBuilder (ТехнологіяМініАвто та ін.) — кон-
кретний будівельник
▷ Реалізує етапи побудови складного об᾿єкта, визна-
чені у базовому класі Builder.
▷ Створює результат побудови (Product) і слідкує за
покроковим конструюванням.
▷ Визначає інтерфейс доступу до результату кон-
струювання.
■ Director (Конвеєр) — розпорядник
▷ Визначає загальний алгоритм конструювання, вико-
ристовуючи реалізації окремих кроків можливості
класу Builder.

40
3. Породжуючі патерни

■ Product (МініАвто та ін.) — продукт


▷ Складний об’єкт, що виходить у результаті кон-
струювання.
Зв’язки між учасниками:
■ Клієнт конфігурує розпорядника (Director) екземп-
ляром конкретного будівельника.
■ Розпорядник викликає методи будівельника для кон-
струювання частин продукту.
■ Конкретний будівельник створює продукт і слідкує
за його конструюванням.
■ Конкретний будівельник представляє інтерфейс до-
ступу до продукту.

3.2.6. Результати використання патерну


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

41
Урок №1

відміну від інших породжуючих патернів, які створюють


продукт миттєво).

3.2.7. Практичний приклад використання патерну


Припустимо, нам потрібно розробити код для ство-
рення різних літальних апаратів. Вони, як ви розумієте,
бувають різними. Уявімо, що у нас є два види літальних
апаратів: дельтаплан і планер. Безперечно, нам потріб-
но буде створити узагальнений клас літального апарату.
Назвемо його «Aircraft». Його код наводимо нижче:
Лістинг 3.2.1. Клас Aircraft

// базовий клас для літального апарату


class Aircraft
{
public:
Aircraft(string);
virtual ~Aircraft();

private:
// тип літального апарату
string aircraftType;
// сховище інформації про літальний апарат
map<string, string> parts;

public:
// отримання інформації про певну частину апарату
string GetPart(const string& key) {
if (!CheckForPart(key)) {
throw "There is no such key!";
}
return parts[key];
}

42
3. Породжуючі патерни

// встановлення значення для конкретної частини


апарату
void SetPart(const string& key,
const string& value) {
parts[key] = value;
}
// перевірка на наявність частини
bool CheckForPart(const string& key) {
return parts.find(key) != parts.end() ?
true:false;
}
// відображення інформації про літальний апарат
void Show();
};

Aircraft::Aircraft(string type)
{
aircraftType = type;
}

Aircraft::~Aircraft()
{
}

void Aircraft::Show() {
cout<<"\n====================\n";

cout<<"Aircraft Type:"<<aircraftType<<endl;

cout << "Frame:" << parts["frame"] << endl;

cout<<"Engine:"<<parts["engine"]<<endl;

cout<<"Wheels:"<<parts["wheels"]<<endl;

cout<<"Doors:"<<parts["doors"]<<endl;
}

43
Урок №1

Тепер перейдемо до класів конкретних будівельни-


ків. Ці класи будуть відповідати за створення двох типів
літаків: дельтаплана і планера. Почнемо з абстрактного
класу будівельника. Він описує конкретні операції для
класів-будівельників:
Лістинг 3.2.1. Базовий клас будівельника

// абстрактний клас будівельника


class AircraftBuilder
{
public:
AircraftBuilder();
virtual ~AircraftBuilder();

protected:
Aircraft* aircraft;

public:
Aircraft* GetAircraft() {
return aircraft;
}

virtual void BuildFrame() = 0;

virtual void BuildEngine() = 0;

virtual void BuildWheels() = 0;

virtual void BuildDoors() = 0;

virtual void BuildWings() = 0;


};

Тепер опишемо клас конкретного будівельника. Це


буде будівельник, який вміє будувати дельтаплани.

44
3. Породжуючі патерни

Лістинг 3.2.2. Клас будівельника дельтапланів

// Клас будівельника. Вміє створювати дельтаплани


class HangGliderBuilder:public AircraftBuilder
{
public:
HangGliderBuilder();
virtual ~HangGliderBuilder();

public:
void BuildFrame();
void BuildEngine();
void BuildWheels();
void BuildDoors();
void BuildWings();
};

HangGliderBuilder::HangGliderBuilder()
{
aircraft = new Aircraft("Hang Glider");
}

HangGliderBuilder::~HangGliderBuilder()
{
delete aircraft;
}

void HangGliderBuilder::BuildFrame() {
aircraft->SetPart("frame", "Hang glider frame");
}

void HangGliderBuilder::BuildEngine() {
aircraft->SetPart("engine", "no engine");
}

void HangGliderBuilder::BuildWheels() {
aircraft->SetPart("wheels", "no wheels");
}

45
Урок №1

void HangGliderBuilder::BuildDoors(){
aircraft->SetPart("doors", "no doors");
}

void HangGliderBuilder::BuildWings() {
aircraft->SetPart("wings", "1");
}

Тепер реалізуємо код будівельника, що вміє створю-


вати планери.
Лістинг 3.2.3. Клас будівельника планерів

// Клас будівельника. Вміє створювати планери


class GliderBuilder : public AircraftBuilder
{
public:
GliderBuilder();
virtual ~GliderBuilder();
public:
void BuildFrame();
void BuildEngine();
void BuildWheels();
void BuildDoors();
void BuildWings();
};

GliderBuilder::GliderBuilder()
{
aircraft = new Aircraft("Glider");
}

GliderBuilder::~GliderBuilder()
{
delete aircraft;
}

46
3. Породжуючі патерни

void GliderBuilder::BuildFrame() {
aircraft->SetPart("frame", "Glider frame");
}

void GliderBuilder::BuildEngine() {
aircraft->SetPart("engine", "no engine");
}

void GliderBuilder::BuildWheels() {
aircraft->SetPart("wheels", "1");
}

void GliderBuilder::BuildDoors() {
aircraft->SetPart("doors", "1");
}

void GliderBuilder::BuildWings() {
aircraft->SetPart("wings", "2");
}

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


тального апарату з використанням конкретного буді-
вельника.
Лістинг 3.2.4. Клас директора

/* клас директора вміє створювати конкретний продукт


за допомогою будівельника */
class AircraftConstructor
{
public:
AircraftConstructor();
virtual ~AircraftConstructor();
void Construct(AircraftBuilder* aircraftBuilder);
};

47
Урок №1

AircraftConstructor::AircraftConstructor()
{
}

AircraftConstructor::~AircraftConstructor()
{
}

void
AircraftConstructor::Construct(AircraftBuilder*
aircraftBuilder)
{
aircraftBuilder->BuildFrame();
aircraftBuilder->BuildEngine();
aircraftBuilder->BuildWheels();
aircraftBuilder->BuildDoors();
}

А тепер давайте розглянемо код всередині main для


розуміння того, як працюватиме наш приклад.
Лістинг 3.2.5. Тіло функції main

#include<iostream>
#include"AircraftBuilder.h"
#include"AircraftConstructor.h"
#include"HangGliderBuilder.h"
#include"GliderBuilder.h"

using namespace std;

int main() {
try {
AircraftBuilder* builder;

48
3. Породжуючі патерни

// Створюємо об’єкт класу директора


AircraftConstructor* shop =
new AircraftConstructor();

// Створюємо об᾿єкт класу будівельника. Цей об᾿єкт вміє


// створювати дельтаплани
builder = new HangGliderBuilder();

// створюємо дельтаплан
shop->Construct(builder);

// показуємо інформацію про дельтаплан


builder->GetAircraft()->Show();

// Створюємо об’єкт класу будівельника.


// Цей об’єкт вміє створювати планери
delete builder;
builder = new GliderBuilder();

// створюємо планер
shop->Construct(builder);

// показуємо інформацію про планер


builder->GetAircraft()->Show();
delete builder;
delete shop;
}
catch (char* str) {
cout << endl << str << endl;
}
return 0;
}

Повний код прикладу ви можете знайти у папці з при-


кладами під назвою Builder.

49
Урок №1

3.3. Factory Method


3.3.1. Назва патерну
Factory Method / Фабричний метод
Також відомий як Virtual Constructor / Віртуальний
конструктор.
Описаний у праці [GoF95].

3.3.2. Мета патерну


Визначає інтерфейс для створення об᾿єкта, але за-
лишає за своїми підкласами рішення, який об᾿єкт треба
створювати [GoF95].

3.3.3. Патерн слід використовувати коли


■ Клас не залежить від конкретного типу створюваного
продукту;
■ Класу невідомий конкретний тип продукту, який треба
створювати;
■ Конкретні типи створюваних продуктів можуть/ма-
ють визначатися в підкласах.

3.3.4. Причини виникнення патерну


Припустимо, нам потрібно розробити гру «стрі-
лялку». У процесі гри герой може вибрати різні типи
вогнепальної зброї. Тип зброї визначає частоту по-
стрілів, калібр кулі, тип кулі (наприклад, звичайна чи
розривна). Для спрощення викладу ідеї вважатимемо,
що конкретний тип зброї може стріляти лише одним
типом куль.

50
3. Породжуючі патерни

І виникає наступне питання. Як пов᾿язати конкрет-


ний клас зброї із типом куль, які підходять для цієї зброї?
Логічно було б покласти обов’язок створення екземп-
ляра кулі на об’єкт класу зброї, з якого виникає постріл.
Отже, кожен із конкретних типів зброї знатиме, до якого
класу належать його кулі. Такий підхід продемонстрова-
но на діаграмі класів на рисунку 3.3.2.

Абстрактна зброя АбстрактнаКуля

+ Стріляти() : АбстрактнаКуля - ПочатковеПоложення: Швидкість


Кулякуля = СтворитиКулю(); - ПочатковаШвидкість: Положення
куля.ПочатковеПоложення = положенняЗброї;
+ ПочатиРух()
куля.ПочатковаШвидкість = ПочатковаШвидкістьКулі;
+ Рухатися()
куля.ПочатиРух();
+ ВразитиМета()
ПовернутиКуля;
# СтворитиКулю() : АбстрактнаКуля «property»
+ ПочатковеПоложення() : Положення
+ ПочатковаШвидкість() : Швидкість

Автомат «створити» КуляАвтомата

# СтворитиКулю() : АбстрактнаКуля + Рухатися()


+ ВразитиМета КуляАвтомата()

Дробовик «створити» КуляДробовика

# СтворитиКулю() : АбстрактнаКуля + Рухатися()


+ ВразитиМета()

Рисунок 3.3.1. Реалізація взаємозв᾿язку


між типами зброї і типами куль

Викладений підхід має такі переваги:


1) клієнтський код не потребує значної уваги до кон-
фігурування екземпляра зброї специфічним йому ти-
пом кулі;

51
Урок №1

2) кожна куля при пострілі конфігурується параме-


трами (початкова швидкість, початкове положення),
про які знає лише зброя, з якої був постріл;
3) клієнтського коду має бути відомо лише про абстрак-
тну кулю, тобто з кожним конкретним екземпляром
кулі клієнт взаємодіє через інтерфейс Абстрактна­Куля
(що дозволяє спростити його архітектуру).
Один із основних недоліків запропонованого підходу
полягає в тому, що тип кулі для конкретного класу зброї
визначається статично на етапі компіляції і не може змі-
нюватись на етапі виконання.
Реалізація продемонстрованої ідеї викладена у п. 3.3.5.

3.3.5. Структура патерну


Загальна структура патерну Factory Method представ-
лена рисунку 3.3.2.

Рисунок 3.3.2. Загальна структура патерну Factory Method

52
3. Породжуючі патерни

Учасники патерну:
■ Creator (АбстрактнаЗброя) — абстрактний розробник
▷ Має абстрактний метод створення екземпляра про-
дукту, тобто делегує виробництво продукту своїм
підкласам.
■ ConcreteCreator (Автомат, Дробовик) — конкретний
розробник
▷ Реалізує метод створення екземпляра продукту.
■ Product (АбстрактнаКуля) — абстрактний продукт
▷ Представлений абстрактним інтерфейсом продукту,
через який працює Creator.
■ ConcreteProduct (КуляАвтомата, КуляДробовика) —
конкретний продукт
▷ Здійснює інтерфейс абстрактного продукту.
Зв᾿язки між учасниками:
■ Creator є абстрактним методом FactoryMethod() ство-
рення екземпляра продукту, який повертає посилання
на Product.
■ Creator покладає відповідальність створення екземп-
ляра конкретного продукту на свої підкласи.
■ ConcreteCreator реалізує метод FactoryMethod(), за-
безпечуючи створення об’єкта класу ConcreteProduct.

3.3.6. Результати використання патерну


Клас Creator залежить від конкретного типу створю-
ваних продуктів.
За створення продукту відповідають підкласи. У цьо-
му аспекті є як переваги, так і недоліки. Переваги поля-

53
Урок №1

гають у тому, що клас Creator поступово уточнює кон-


кретні продукти у своїх підкласах. Недолік: для реалізації
можливості створювати новий тип продуктів необхідно
створювати новий підклас Creator, що може призвести
до невиправданого зростання кількості його підкласів.
Дозволяє об᾿єднати дві паралельні ієрархії класів
творців і продуктів.
Можливе визначення реалізації фабричного методу
за замовчуванням. Найчастіше, фабричний метод є аб-
страктним, що вимагає його обов’язкового визначення
в підкласах. Якщо йому визначити реалізацію за умов-
чанням, він буде додатковою можливістю модифікувати
можливості класу при успадкування.
Конкретний клас створюваного продукту може пере-
даватися як тип-параметр класу Creator або його підкла-
сів. У такому випадку фабричний метод створюватиме
екземпляри узагальненого типу-параметра, і для визна-
чення нового типу продуктів не обов’язково використо-
вувати наслідування. Такий варіант реалізації може бути
легко використаний у C++ або C#, але буде неприйнятним,
наприклад, для Java (принаймні, для версій 1.6 і нижче).

3.3.7. Практичний приклад використання патерну


Реалізуємо ідею, представлену у п. 3.3.2. Опис класів
куль представлений у лістингу 3.3.1. Слід звернути ува-
гу на абстрактні методи: HitTarget() — реалізує попадан-
ня в ціль, і Movement() — реалізує траєкторію польоту
кулі. Реалізація цих методів визначається підкласами кла-
су AbstractBullet. Це означає, що наслідування від класу

54
3. Породжуючі патерни

AbstractBullet забезпечує реалізацію поведінки, специ-


фічної для конкретного типу кулі.
Лістинг 3.3.1. Реалізація класів куль

/*
* Точка в тривимірному просторі.
* Використовується для визначення положення.
*/
struct Point3D {
int X;
int Y;
int Z;
};

/*
* Вектор у тривимірному просторі.
* Використовується для визначення напрямку.
*/
struct Vector3D {
int X;
int Y;
int Z;
};

/*
* Клас абстрактної кулі.
*/
class AbstractBullet
{
private:
Point3D location;
Vector3D direction;
double caliber;
public:
/*
* Поточне положення кулі
*/

55
Урок №1

Point3D GetLocation() {
return location;
}

void SetLocation(const Point3D& newLocation) {


location = newLocation;
}

/*
* Напрямок кулі
*/
Vector3D GetDirection() {
return direction;
}

void SetDirection(const Vector3D& newDirection) {


direction = newDirection;
}

/*
* Калібр кулі
*/
double GetCaliber() {
return caliber;
}

void SetCaliber(double newCaliber) {


caliber = newCaliber;
}

/*
* Початок руху кулі.
*/
void StartMovement()
{
// Реалізація початку руху
}

56
3. Породжуючі патерни

/*
* Метод ураження мети.
* Оскільки різні типи куль вражають ціль
* по-різному, то метод має реалізовуватися
* в підкласах.
*/
virtual void HitTarget(void* target) = 0;

/*
* Метод, що реалізує рух кулі.
* Оскільки різні типи куль мають різну
* траєкторію руху, то метод має реалізовуватися
* в підкласах.
*/
virtual void Movement() = 0;
};

/*
* Клас кулі для автоматичної зброї.
*/
class AutomaticBullet : public AbstractBullet
{
public:
void HitTarget(void* target){
// реалізація ураження цілі target
cout << "Hit by automatic bullet\n";
}

void Movement(){
// реалізація алгоритму руху кулі
}
};

57
Урок №1

/*
* Клас кулі для дробовика.
*/
class ShotgunBullet : public AbstractBullet
{
public:
void HitTarget(void* target){

// реалізація ураження цілі target


cout << "Hit by shotgun bullet\n";
}

void Movement(){
// реалізація алгоритму руху кулі
}
};

Реалізація класів зброї представлена у лістингу 3.3.2.


Метод Shoot() цього класу реалізує постріл. Технологія
пострілу однакова для всіх типів зброї. Для створення
кулі, специфічної для конкретного типу зброї, виклика-
ється фабричний метод CreateBullet(), який реалізується
у кожному з підкласів. Цей метод повертає посилання на
AbstractBullet, під яким у кожному конкретному випадку
буде представлений об’єкт відповідного класу кулі.
Лістинг 3.3.2. Реалізація класів зброї

/*
* Клас абстрактної зброї
*/
class AbstractWeapon {
protected:

58
3. Породжуючі патерни

/*
* Фабричний метод створення кулі.
*/
virtual AbstractBullet* CreateBullet() = 0;
private:
Point3D location;
Vector3D direction;
double caliber;

public:
/*
* Поточне положення зброї
*/
Point3D GetLocation() {
return location;
}

void SetLocation(const Point3D& newLocation) {


location = newLocation;
}
/*
* Напрямок зброї
*/
Vector3D GetDirection(){
return direction;
}
void SetDirection(const Vector3D& newDirection) {
direction = newDirection;
}

/*
* Калібр зброї
*/
double GetCaliber() {
return caliber;
}

59
Урок №1

void SetCaliber(double newCaliber) {


caliber = newCaliber;
}

/*
* Метод, що робить постріл.
* Повертає екземпляр створеної кулі.
*/
AbstractBullet* Shoot(){
// створення об’єкта кулі за допомогою
// фабричного методу
AbstractBullet* bullet = CreateBullet();
// налаштування кулі на поточні параметри
// зброї
bullet->SetCaliber(this->GetCaliber());
bullet->SetLocation(this->GetLocation());
bullet->SetDirection(this->GetDirection());
// почати рух кулі
bullet->StartMovement();
// повернути екземпляр кулі
return bullet;
}
};

/*
* Клас автоматичної зброї.
*/
class AutomaticWeapon : public AbstractWeapon
{
public:
AutomaticWeapon(){
this->SetCaliber(20);
}
protected:
/*
* Реалізація фабричного методу.

60
3. Породжуючі патерни

* Створює екземпляр кулі,


* специфічний для поточного типу зброї.
*/
AbstractBullet* CreateBullet(){
return new AutomaticBullet();
}
};

/*
* Клас дробовика.
*/
class Shotgun : public AbstractWeapon
{
public:
Shotgun(){
this->SetCaliber(50);
}
protected:
/*
* Реалізація фабричного методу.
* Створює екземпляр кулі,
* специфічний для поточного типу зброї.
*/
AbstractBullet* CreateBullet()
{
return new ShotgunBullet();
}
};

Запропонований фрагмент реалізації моделі «зброя —


куля» демонструє лише її архітектурні особливості і не
претендує на функціональну повноту (у реальній ігровій
програмі, скоріш за все, технологія пострілу виглядала
набагато складнішою).

61
Урок №1

Діаграма класів описаної моделі представлена на ри-


сунку 3.3.2.

Рисунок 3.3.2. Діаграма класів моделі «зброя — куля»

Запропонований підхід має такі переваги:


1) ризик того, що зброя залишиться без об’єкта кулі в
момент пострілу або будуть вироблятися кулі не ха-
рактерного типу зброї, зведений до мінімуму, оскільки
за створення кулі відповідає сама зброя;
2) клієнтський код, що використовує зброю та кулю, не
залежить від того, яким типом кулі проводиться постріл,
йому достатньо тільки знати про клас AbstractBullet;
3) відповідальність за конфігурування зброї конкрет-
ним типом вироблених куль лягає більше не на клі-

62
3. Породжуючі патерни

єнтський код, а на конкретний клас зброї, що дозволяє


спростити архітектуру клієнта.
Недоліки запропонованого рішення:
1) тип куль, які підходять під конкретний вид зброї,
визначається статично на етапі компіляції. Його не
можна змінювати на етапі виконання, тому заданий
тип зброї може створювати лише один вид куль;
2) для реалізації можливості стріляти новими куля-
ми треба реалізувати новий клас зброї, що, зрештою,
може призвести до зростання ієрархії класів зброї.
Повний код прикладу міститься в папці з приклада-
ми до цього уроку. Назва проєкту FactoryMethod.

3.4. Prototype
3.4.1. Назва патерну
Prototype/Прототип.
Описаний у праці [GoF95].
3.4.2. Мета патерну
Визначає види створюваних об᾿єктів за допомогою
екземпляра-прототипу і створює нові об᾿єкти шляхом
копіювання цього прототипу [GoF95].

3.4.3. Патерн слід використовувати коли:


■ об’єкти мають створювати клієнтський код, нічого не
знаючи про їх клас або про те, які дані вони містять;
■ класи створюваних об’єктів визначаються під час ви-
конання (наприклад, при динамічному завантаженні);

63
Урок №1

■ екземпляри класу можуть перебувати в невеликій


кількості станів, тому може виявитися, що зручніше
створити декілька прототипів і клонувати їх замість
створення екземпляра класу.

3.4.4. Причини виникнення патерну


Уявімо, що в нас є завдання — розробити гру «Тетріс»,
зокрема, «Будівництво». Для тих, хто ніколи не грав у цю
захоплюючу гру, коротко викладемо суть. З верхньої ча-
стини ігрового поля падають будівельні блоки-цеглин-
ки різної форми. Завдання гравця — не дозволити бу-
дівельним блокам повністю заповнити усе ігрове поле.
Для цього треба намагатися розташовувати блоки так,
щоб горизонтальний ряд ігрового поля був заповнений,
оскільки повністю заповнені лінії зникають.
Набір блоків-цеглинок різних форм фіксований. Про-
грама рандомно вибирає форму наступного блоку, ство-
рює блок відповідно до обраної форми і починає просу-
вати його по ігровому полю.
І виникає наступне питання. Як створювати екземп-
ляри будівельних блоків, мінімізувавши при цьому за-
лежність клієнтського коду від типу форми блока?
Можна запропонувати такі рішення. Нехай кожен
об’єкт будівельного блоку вміє створювати копію себе за
допомогою якогось універсального інтерфейсу. У програ-
мі є список блоків-прототипів, кожен із яких представ-
ляє одну з можливих форм. При необхідності створен-
ня наступного блоку, його прототип обирається рандо-
мно зі списку. Після чого створюється новий екземпляр

64
3. Породжуючі патерни

будівельного блоку за допомогою клонування обраного


прототипу. Оскільки універсальний інтерфейс передба-
чений для клонування, то клієнтський код повністю не
залежить від форми, яку йому треба клонувати.

3.4.5. Структура патерну


Загальна структура патерну представлена на рисун-
ку 3.4.1.

Рисунок 3.4.1. Загальна структура патерну Prototype

Учасники патерну:
■ Prototype — прототип
▷ Визначає інтерфейс для самоклонування (у мові
C# для таких цілей можна використовувати стан-
дартний інтерфейс ICloneable).
■ ConcretePrototype — конкретний прототип
▷ Реалізує операцію самоклонування.

65
Урок №1

■ Client — клієнт
▷ Створює новий об’єкт, надсилаючи запит прототипу
«копіювати себе».
Зв’язки між учасниками: Клієнтський код звертаєть-
ся до прототипу «створити копію себе».

3.4.6. Результати використання патерну


Як і всі патерни, що породжують, прототип прихо-
вує від клієнта конкретні класи продуктів, зменшуючи
тим самим його складність. Нові класи продуктів можна
створювати майже без модифікацій клієнтського коду.
Додавання/видалення нових типів продуктів під час
виконання. Об’єкти-прототипи можна створювати і ви-
даляти на етапі виконання. Таке рішення є більш гнуч-
ким у порівнянні з Abstract Factory або Factory Method.
Визначення нових типів продуктів без необхідності
успадкування. Для створення нового типу продукту тре-
ба визначити його прототип, що у загальному випадку
не вимагає успадкування. Це часто дозволяє позбутися
великих ієрархій класів, які потрібні при використанні
патернів Abstract Factory або Factory Method.
Використання диспетчера прототипів. Найчастіше,
для керування прототипами у системі використовуєть-
ся спеціальний диспетчер, у якому реєструються прото-
типи. Клас-диспетчер дозволяє отримувати необхідний
прототип за назвою, а також динамічно додавати або ви-
даляти прототипи.
Відсутність параметрів у методі Clone() забезпечує
найзагальніший інтерфейс для клонування.

66
3. Породжуючі патерни

Реалізація методу Clone() — найважча частина при


використанні патерну. Особливі труднощі виявляються
тоді, коли треба клонувати об’єкти, які він інкапсулює, і
кругові посилання. Найчастіше базова реалізація клону-
вання вже реалізована на рівні основної бібліотеки мови
програмування (наприклад, C# або Java).

3.4.7. Практичний приклад використання патерну


Давайте реалізуємо патерн Prototype у практичному
прикладі. Створімо ієрархію успадкування пристроїв.
Базовим класом ієрархії буде Device. Він визначить базо-
вий інтерфейс і головним у ньому буде визначення суто
віртуальної функції-члена Clone. Ми будемо створювати
реалізацію цієї функції в нащадках класу Device.
Лістинг 3.4.1. Клас Device

/*
* Це абстрактний базовий клас Device.
* Він визначає функцію Clone, яка складає
* основу патерну Prototype
*/
class Device {
private:
// назва пристрою
string name;
public:
// конструктора
Device() : Device("Unknown device") {}
Device(string dname){
SetName(dname);

67
Урок №1

// допоміжні функції
string GetName() const{
return name;
}
void SetName(string dname) {
name = dname;
}
// Суто віртуальна функція
// Вона використовуватиметься для створення копій
virtual Device* Clone() const = 0;

// відображення даних
void Show() const{
cout << "\nName is\n" << GetName() << "\n";
}
};

Слід звернути увагу на те, що метод Clone() повертає


покажчик типу Device*. Це змушує нащадка створювати
свою реалізацію Clone. Тепер стосовно нащадка. У нашому
випадку, нащадком буде клас «Car». Він характеризує маши-
ну. Ми включили до нього лише деякі властивості машини.
Головним для нас є реалізація функції-члена «Clone». Вона
створить копію машини, коли в цьому виникне потреба.
Лістинг 3.4.2. Клас Car

/*
* Конкретний нащадок пристрою клас Car
*/
class Car : public Device {
private:
// властивості машини
string manufacturer;

68
3. Породжуючі патерни

string description;
string color;
int year;

public:
// конструктора
Car():Car("No information", "No description",
"No color", 0){
SetName("Car");
}
Car(string cmanufacturer, string cdescription,
string ccolor, int cyear);
public:
// допоміжні функції
int GetYear()const{
return year;
}
string GetManufacturer()const{
return manufacturer;
}
string GetDescription()const{
return description;
}
string GetColor()const{
return manufacturer;
}

void SetYear(int cyear){


year = cyear;
}
void SetManufacturer(string cmanufacturer) {
manufacturer = cmanufacturer;
}
void SetColor(string ccolor) {
color = ccolor;
}

69
Урок №1

void SetDescription(string cdescription) {


description = cdescription;
}

// реалізація віртуальної функції у нащадку


Device* Clone()const;
void Show() const;
};

// реалізація конструктора
Car::Car(string cmanufacturer, string cdescription,
string ccolor, int cyear) {
SetName("Car");
SetManufacturer(cmanufacturer);
SetDescription(cdescription);
SetColor(ccolor);
SetYear(cyear);
}

// функція клонування
Device* Car::Clone() const{
Car* tempCar = new Car();

/* Зверніть увагу!
* При роботі з динамічною пам’яттю потрібно
* визначити конструктор копіювання, конструктор
* перенесення і перевантажити оператор =
*/
*tempCar = *this;
return tempCar;
}

// Відображення на екрані даних


void Car::Show() const{
Device::Show();
cout << "\nDescription of car is\n"
<< GetDescription() << "\n";

70
3. Породжуючі патерни

cout << "\nManufacturer of car is\n"


cout << GetManufacturer() << "\n";
cout << "\nYear of car is\n" << GetYear()
<< "\n";
cout << "\nColor of car is\n" << GetColor()
<< "\n";
}

І ще раз звертаємо вашу увагу, що ми не враховува-


ли динамічне виділення пам’яті в класі «Car». Якщо ця
ситуація виникне, нам потрібно буде реалізувати кон-
структор копіювання, оператор =, конструктор переносу.
Лістинг 3.4.5. Клієнтський код,
що використовує класи з прототипуванням

int main() {

// Ввносимо дані
string manufacturer;
cout << endl << "Input manufacturer of car:"
<< endl;
std::getline(std::cin, manufacturer);

string description;
cout << "Input description of car:" << endl;
std::getline(std::cin, description);

string color;
cout << "Input color of car:" << endl;
std::getline(std::cin, color);

int year;
cout << "Input year of car:" << endl;
cin >> year;

71
Урок №1

// створимо об’єкт
Car c(manufacturer, description, color, year);
c.Show();

cout << "Let’s clone!\nLet’s prototype!" << endl;

// клонуємо об’єкт
Car* copy = (Car*)c.Clone();
copy->Show();

// видаляємо клонований об’єкт


delete copy;

return 0;
}

Суть коду з лістингу 3.4.5 така:


1) Вводимо дані про машину;
2) Створюємо об’єкт машини;
3) Створюємо копію машини за допомогою клону-
вання і виклику функції-члена Clone.

Приклад показує, що реалізація патерна Prototype не


надто складна в порівнянні з іншими патернами. Повний
код прикладу міститься в папці прикладів до цього уро-
ку. Назва проєкту Prototype.

3.5. Singleton
3.5.1. Назва патерну
Singleton/Одинак.
Описаний у праці [GoF95].

72
3. Породжуючі патерни

3.5.2. Мета патерну


Гарантує, що клас має лише один екземпляр і надає
одну точку доступу до нього [GoF95].

3.5.3. Патерн слід використовувати


Коли має існувати лише один екземпляр заданого
класу, доступний для всього клієнтського коду [GoF95].

3.5.4. Причини виникнення патерну


Часто у програмуванні трапляється ситуація, коли
треба визначити деяку змінну, яка доступна глобально і
може існувати лише в одному екземплярі. Наприклад, для
доступу до системи бухгалтерського обліку організації ко-
ристувачеві необхідно пройти автентифікацію, вказавши
своє ім᾿я і пароль. Після успішної автентифікації корис-
тувач отримує доступ до системи. При цьому, у кожний
момент часу системі має бути відома персональна інфор-
мація користувача: його повне ім᾿я, рівень доступу та ін.
Для реалізації такої можливості слід визначити деякий
об᾿єкт, який доступний з кожної точки системи (напри-
клад, для визначення того, чи є доступ у користувача до
певних можливостей системи) і може існувати тільки в
одному екземплярі (оскільки в системі не можуть автен-
тифікуватися одночасно два і більше користувачів).
У мові C++ це можна реалізувати за допомогою вико-
ристання глобальної змінної або статичного поля класу.
Але такий підхід має недоліки:
1) клієнтський код має вільний доступ до цієї змінної,
що може призвести до її несанкціонованих змін;

73
Урок №1

2) у клієнтському коді існує потенційна можливість


створення більше одного екземпляра такої змінної.
Слід зазначити, що у повністю об᾿єктно-орієнтованих
мовах, якими є C# і Java, взагалі виключена можливість
визначення глобальних змінних. Тому, у таких мовах для
визначення глобального стану можна використовувати
лише статичні поля класу.
Для подолання недоліків представленої ідеї, скори-
стаємося таким підходом:
1. Можливість створення власного екземпляра повинен
мати лише сам клас. В такому разі легко проконтро-
лювати кількість створених екземплярів і, за необ-
хідності, обмежити його одним об’єктом.
2. Доступ до свого єдиного екземпляра також повинен
надавати сам клас.
3. Передбачити можливість модифікації поведінки об’єк-
та-одинака через наслідування так, аби при заміні
єдиного екземпляра об’єктом його підкласу клієнт-
ський код не вимагав модифікацій.
Технічна реалізація запропонованого підходу може
мати такий вигляд:
1. Для заборони створення екземплярів класу зовніш-
нім кодом його конструктор (або конструктори) ви-
значається як protected.
2. Доступ до екземпляра класу здійснюється у вигляді
його публічного статичного методу.
3. Члени класу єдиного екземпляра (крім змінної, в
якій зберігається посилання на єдиний об’єкт, і ме-

74
3. Породжуючі патерни

тоду надання доступу до цієї змінної) мають бути


нестатичними. Це дозволить, при необхідності, мо-
дифікувати поведінку класу, успадковуючи від ньо-
го підкласи.

3.5.5. Структура патерну


Загальна структура патерну представлена на діагра-
мі 3.5.1.

Рисунок 3.5.1. Загальна структура патерну Singleton

Учасники патерну:
■ Singleton — одинак
▷ Забезпечує створення лише одного екземпляра са-
мого себе, посилання на який зберігається в ста-
тичній змінній uniqueInstance і глобальний доступ
до нього через статичний метод Instance().
▷ Забороняє клієнтському коду створювати власні
екземпляри, заборонивши йому доступ до свого
конструктора (конструктор одинак визначається
як захищений, або приватний).

75
Урок №1

Зв’язки:
■ Клієнтський код має можливість доступу до екземпляра
Singleton лише через його статичний метод Instance().

3.5.6. Результати використання патерну


Є можливість повного контролю доступу клієнта до
єдиного екземпляра. Що, наприклад, може дати мож-
ливість підрахувати кількість клієнтських звернень або
забороняти доступ у разі відсутності відповідних прав.
Єдині екземпляри об’єкта мають більші можливості,
ніж звичайні глобальні змінні.
Допускається уточнення поведінки з використанням
наслідуванням.
Допускається можливість створення більш ніж од-
ного екземпляра. Тому, достатньо модифікувати метод
Instance().
Використання одинака надає більш гнучкі можли-
вості, ніж статичні функції класів або статичні класи C#,
оскілки такі програмні конструкції, як правило, мають
ряд обмежень, що роблять не можливим визначення вір-
туальних функцій з наступним заміщенням їх у підкласах.
Використання глобальних змінних і одинаків сприяє
створенню «брехливих» інтерфейсів, тобто інтерфейсів,
які не показують явних залежностей з іншими необхід-
ними для його функціонування об’єктами.
При реалізації одинака в багатопотоковому середо-
вищі слід також враховувати той факт, що два паралельні
потоки можуть водночас отримувати доступ до методу
створення єдиного екземпляра, що може призвести до

76
3. Породжуючі патерни

створення більше одного екземпляра одинака. Щоб уник-


нути ситуації «боротьби за ресурси», слід помістити код,
відповідальний за створення об’єкта, у критичну секцію,
надавши цим доступом до нього лише одному потоку.

3.5.7. Практичний приклад використання патерну


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

77
Урок №1

У класі Logger оголосимо статичну змінну-член типу


Logger* з назвою pObj, в якій зберігатимемо адресу єди-
ного об᾿єкта. Для доступу до цієї змінної із зовнішньо-
го коду створимо статичну функцію-член GetInstance.
Завданням цієї функції є повернення покажчика на єди-
ний об᾿єкт типу Logger. У разі рівності pObj NULL ми
виділяємо пам’ять для єдиної копії Logger. У такий спо-
сіб реалізується створення журналу на вимогу: якщо у
процесі роботи програми не було звернень до журналу,
його екземпляр не створюється.
Для виключення можливості створення більше одно-
го екземпляра Logger у процесі роботи програми мож-
ливість створювати його об’єкт повинен мати лише сам
клас Logger. Для цього єдиний конструктор класу оголо-
шується закритим.
Повна реалізація класу Logger представлена у ліс-
тингу 3.5.1.
Лістинг 3.5.1. Реалізація класу Logger

/*
* Клас журналу подій програми.
* Призначення — запис подій у спеціальний текстовий файл.
* У програмі може існувати тільки в одному екземплярі.
*/
class Logger {
private:
/* конструктор закритий, щоб не було можливості
* створювати копію об’єкта в
* обхід спеціальній функції
*/
Logger() {}

78
3. Породжуючі патерни

// покажчик на майбутній об’єкт логера


static Logger* pObj;
public:
// статична функція-член для отримання доступу
// до об’єкту
static Logger* GetInstance();

// функція-член для запису рядків у лог-файл


void PutMessage(string message);
};

Logger* Logger::pObj = NULL;

// реалізація функції-члена для отримання доступу


// до об’єкту
// Об’єкт створюється, якщо він не існував.
// Якщо він існував, покажчик повертається на вже
// створений об’єкт
Logger* Logger::GetInstance() {
if (!pObj) {
pObj = new Logger();
}
return pObj;
}

// Реалізація функції запису рядків у лог-файл


void Logger::PutMessage(string message) {
const time_t timer = time(NULL);
ofstream fObj("logsingleton.txt", ios::app);
if (!fObj) {
cout << "\nError with file\n";
exit(EXIT_FAILURE);
}
fObj << message.c_str() << "\t" << ctime(&timer)
<< endl;
}

79
Урок №1

Повний код прикладу міститься в папці з приклада-


ми до цього уроку. Назва проєкту Singleton.

3.6. Аналіз і порівняння породжуючих патернів


Є два найбільш поширені способи параметризації
системи класами створюваних об’єктів.
Перший спосіб полягає у застосуванні успадкування:
для створення нового об’єкта створюється новий клас.
Такий підхід відповідає патерну Factory Method. Як вже
було зазначено, основними недоліками цього підходу є
статичне визначення класів створюваних об’єктів і не-
безпека створення великої кількості класів, які нічого
не роблять, окрім як реалізують фабричний метод для
створювання об’єктів.
Другий спосіб ґрунтується на композиції: визначаєть-
ся об’єкт, якому відомо про класи об’єктів-продуктів. Цей
об’єкт робиться параметром системи і може змінюватись
на етапі виконання. Такий підхід притаманний патернам:
Abstract Factory, Builder, Prototype. Кожен із цих патернів
створює спеціальний «фабричний об’єкт» для створювання
продуктів. У разі Abstract Factory такий об’єкт відповідає
за створення сімейства продуктів. У разі використання
Builder, фабричний об’єкт відповідає за покрокове ство-
рення складного продукту. Паттерн Prototype передбачає
об’єкт, який має функцію самоклонування.
Дещо відокремлено серед інших породжуючих па-
тернів стоїть Singleton. Його призначення — створення
глобального об’єкта-одинака. Часто абстрактна фабрика
реалізується як одинак, якщо немає сенсу створювати для

80
3. Породжуючі патерни

неї більше одного екземпляра. Singleton також може ви-


користовуватися при реалізації патерну Prototype. При-
клад цього — реалізація палітри конфігурацій, описана
у пункті 3.4.7.
Оскільки Singleton є аналогом глобальної змінної
(а працювати з глобальними змінними завжди треба з
особливою обережністю), то в сучасному програмуван-
ні Singleton відносять до антипатернів і намагаються, по
можливості, не використовувати.

81
Урок №1

4. Домашнє завдання

1. Спроєктувати універсальний каркас багатодокумент-


ного редактора. Редактор має містити основні функ-
ції роботи з документом:
■ Створення;
■ Відкриття;
■ Збереження;
■ Збереження під новою назвою;
■ Друк;
■ Закриття.
Запропонований об᾿єктно-орієнтований дизайн кар-
каса редактора має без змін використовуватися для
розробки редакторів різних типів документів.
2. Беручи за основу каркас, розроблений у задачі 1, спро-
єктувати редактор, призначений для роботи з тексто-
вими документами.
3. На підставі каркаса, розробленого в задачі 1, спроєк-
тувати редактор, призначений для роботи з графічни-
ми документами різних форматів. Редактор повинен
мати можливість зберігати зображення у вибраному
графічному форматі, а також мати палітру інструмен-
тів для обробки зображення.

82
5. Використані інформаційні джерела

5. Використані
інформаційні джерела
[GoF95] Гамма Е., Хелм Р., Джонсон Р., Вліссідес Дж. Прийоми об’єк-
тно-орієнтованого проєктування. Патерни проєктуван-
ня. — СПб: Пітєр, 2001. — 386 с.
[Grand2004] Гранд М. Шаблони проєктування у Java / М. Гранд;
Пер. з англ. С. Бєліковой. — М.: Новоє знаніє, 2004. — 559 с.
[SM2002] Стелтінг С., Маасен О. Використання шаблонів Java. Бібліо-
тека професіонала: Пер. з англ. — М.: Видавничий дім
«Вільямс», 2002. — 576 с.
[DPWiki] Шаблони проєктування (Wikipedia)
[DPOverview] Огляд патернів проєктування

83
Урок №1
Породжуючі патерни проєктування

© Комп᾿ютерна Академія «Шаг», www.itstep.org

Усі права на фото-, аудіо- і відеотвори, що охороняються авторським правом і


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

You might also like