В попередній статті серії ми з вами розібрали Абстрактну Фабрику. Сьогодні ж розберемо патерн програмування – Будівельник. Цей шаблон також належить до групи Породжуючих шаблонів.
В цій статті розглянемо наступні питання:
- означення шаблона,
- приклад застосування,
- коли застосовувати, його плюси і мінуси,
- порівняємо даний шаблон із іншими породжуючим шаблонами,
- а також розглянемо доцільність використання у мові програмування Python.
Отже,
Означення
Даний шаблон розділяє процес створення складного об’єкта від його представлення, таким чином що в результаті одного і того ж процесу конструювання можна отримувати різні представлення об’єкта.
Як завжди без прикладу нічого не зрозуміло, тому одразу на практиці розберемо патерн 😉
Приклад
Уявіть, що ви прийшли в МакДональдс і захотіли замовити дитяче меню (згадати дитинство 😉 – набір HappyMeal. Дане меню складається з основної страви, додаткової страви, напою та іграшки. Наприклад, це може бути: гамбургер, картапля фрі, кола і іграшка динозавр.
Зауважте, що можливі різні варіанти кожного із елементів у даному дитячому наборі, але процес створення – однаковий. Незважаючи на те чи ви замовляєте чізбургер, курку чи гамбургер – процес однаковий:
Працівник за стійкою керує усією командою, щоб зібрати докупи ваш набір – головну страву, додаткову та іграшку. Усі ці елементи попадають у сумочку. Напій йде окремо в стаканчику.
В даному прикладі ми (як клієнти, замовники) можемо замовити HappyMeal і вказати, які саме страви увійдуть до нього на місце основної страви та напою. Людина за стійкою (касир) отримує замовлення від нас (клієнта) та передає далі команді. Команда збирає докупи 4 елементи, базуючись на нашому замовленні. І ми (клієнти) отримуємо назад готовий набір.
Тепер, якщо розбити по ролях відносно патерна Будівельник: ми виступаємо у ролі Клієнта (Client), Касир у ролі Розпорядника (Director – надає команди будівельнику), Команда закладу у ролі Абстрактного Базового Будівельника (Base Builder – може підготувати набір по будь-якому замовленню), а вже Команда, яка працює над конкретним замовленням – у ролі Конкретного Будівельника (Concrete Builder – готує конкретний набір по конкретному замовленні).
В реалізації даного прикладу Будівельник допоможе нам виокремити процес створення замовлення від конкретного набору елементів, які Клієнт захотів отримати.
А тепер розберемо детальніше усі ролі в патерні Будівельник:
Основні ознаки та дійові особи патерна
Патерн Будівельник визначає алгоритм поетапного конструювання складного продукта (об’єкта) від його зовнішнього представлення. Таким чином, що з допомогою одного і того ж алгоритма можна отримувати різні представлення даного продукта.
Поетапне створення продукта означає його створення частинами. Після того, як побудована остання його частина, продукт можна використовувати.
Для цього патерн Будівельник визначає алгоритм поетапного створення продукту в спеціальному класі Director (розпорядник), а відповідальність за координацію процесу збірки окремих частин продукта покладає на ієрархію класів Будівельника (Builder).
В цій ієрархії базовий клас Будівельника (Builder) декларує інтерфейси (протокол) для побудови окремих частин продукту, а відповідні підкласи Будівельника (ConcreteBuilder) їх реалізують потрібним чином.
Основні ознаки патерна Будівельник:
- вирішує проблему обробки подібних вхідних даних та потребу їх різних презентацій (вихідних даних);
- ховає розбір вхідних даних в класі Розпорядника (Director);
- декларує спільний стандартний протокол для створення всеможливих презентацій вихідних даних;
- тримає усі кроки даного протоколу в інтерфейсі Будівельиника;
- визначає підклас Будівельника для кожної із презентацій даних;
- Клієнт створює об’єкт Розпорядника та Будівельник, і зв’язує їх між собою. А точніше передає Будівельник Розпоряднику;
- Клієнт дає команду Розпоряднику – створити об’єкт;
- Клієнт дає команду Будівельнику – повернути результат.
Коли Використовується і порівняння з іншими породжуючими патернами
Патерн Будівельник може допомогти у вирішенні наступного роду задач:
- Якщо в системі існують складні об’єкти, створення яких за одну операцію є складно або неможливо. Потрібна поетапна побудова об’єктів з контролем результатів на кожному етапі.
- Дані повинні мати кілька різних представлень. Класичний приклад. Маємо деякий вихідний документ у форматі RTF (Rich Text Format), який містить текст, зображення, інформацію про формат. І його потрібно представити у форматі Microsoft Word та звичайний текстом. Кожен з форматів і буде представленням даного документу.
Деколи породжуючі патерни є взаємодоповнюючими. Будівельник може використовувати інші патерни, щоб створювати продукти. Абстрактна Фабрика, Будівельник та Прототип можуть використовувати Синглтон у своїй реалізації (більше про це у наступних статтях).
Будівельник фокусується на конструкції складних об’єктів крок за кроком. В той час як Абстрактна Фабрика наголошує на створення сімейства об’єктів (складних або простих – неважливо). Будівельник повертає продукт (об’єкт) як кінцевий крок, а Абстрактна Фабрика повертає продукт одразу.
Досить часто ваша програма може спочатку використовувати патерн Метод Фабрики (простіший та легший у кастомізації), а вже потім, із ускладненням вашого коду, переходити до використання Абстрактної Фабрики, Прототипа чи Будівельника, які надають більшої гнучкості в процесі створення нових об’єктів.
Будівельник в мові Python
Ну і нарешті, трохи коду. Коду на мові Python звичайно.
Розберемо ще один приклад. Виробництво автомобілів. Покажемо на прикладі виробництва автомобілів двох різних типів: Джипа та легковика Нісан, застосування патерна Будівельник.
Отже, код з детальними пояснюючими коментарями:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# -*- coding: utf-8 -*- # наш основний продукт - Автомобіль class Car: """ Клас Автомобіля. Автомобіль збирається докупи Розпорядником (Directory клас) на базі отриманих від Будівельника (Builder) авто частин. Тобто Будівельник та Розпорядник обидвоє мають вплив на конфігурацію кінцевого автомобіля. """ def __init__(self): # наш автомобіль має: self.__wheels = list() # колеса self.__engine = None # мотор self.__body = None # каркас def setBody(self, body): # встановлюємо каркас self.__body = body def attachWheel(self, wheel): # встановлюємо колеса self.__wheels.append(wheel) def setEngine(self, engine): # встановлюємо мотор self.__engine = engine def specification(self): # роздруковуємо деталі автомобіля print "тип кузовва: %s" % self.__body.shape print "к-сть кінських сил: %d" % self.__engine.horsepower print "розмір колеса: %d" % self.__wheels[0].size # визначаємо класи частин автомобіля class Wheel: """Клас Колеса. Його розмір. """ size = None class Engine: """Клас Мотора. Кількість кінських сил. """ horsepower = None class Body: """Клас Кузова Автомобіля. Напр: хетчбек, джип, седан. """ shape = None class Director: """Розпорядник. Контролює процес створення продукту. Розпорядник асоціються з Будівельником. Таким чином Розпорядник делегує побудову менших частин продукта Будівельнику і збирає їх докупи. Над Розпорядник буде збирати Автомобіль та повертати Клієнту. """ # резервуємо атрибут класу для Будівельника __builder = None # метод, який запам'ятовує переданого будівельника def setBuilder(self, builder): self.__builder = builder # даний метод збирає (будує) автомобіль, використовуючи напередодні # переданого Розпоряднику Будівельника def getCar(self): # створюємо базовий автомобіль (наш основний продукт) car = Car() # встановлюємо тип кузова автомобіля, який нам дає Будівельник body = self.__builder.getBody() car.setBody(body) # встановлюємо мотор, який нам дає Будівельник engine = self.__builder.getEngine() car.setEngine(engine) # встановлюємо 4 колеса, який нам дає Будівельник i = 0 while i < 4: wheel = self.__builder.getWheel() car.attachWheel(wheel) i += 1 # ну і наприкінці повертаємо налаштований, повністю зібраний автомобіль return car class Builder: """ Базовий клас Будівельника, який оголшує протокол створення автомобіля будь-якого типу. Цей клас відповідає за створення усіх частин автомобіля. З нього унаслідуються конкретні класи Будівельники для конкретних типів автомобілів. """ # кожен із даних методів класу повинен бути перевизначеним та реалізованим # у підкласах будівельників конкретних типів автомобілів def getWheel(self): pass def getEngine(self): pass def getBody(self): pass class JeepBuilder(Builder): """ Клас конкретного Будівельника - ConcreteBuilder. Даний будівельник будує частини для автомобіля типу Джип. """ def getWheel(self): wheel = Wheel() # розмір колеса у джипа велики! wheel.size = 22 return wheel def getEngine(self): engine = Engine() # сил кінських також багато engine.horsepower = 400 return engine def getBody(self): body = Body() # тип кусоза Оффроад, на англійський є стандарт: SUV body.shape = "SUV" return body class NissanBuilder(Builder): """ А тепер легковушки Нісан. Хечбеки. Даний будівельник також успадковує клас базового абстрактного будівельника. І готує частини для автомобілів сімейства Нісан. """ def getWheel(self): wheel = Wheel() # тут уже поменші колеса wheel.size = 16 return wheel def getEngine(self): engine = Engine() # слабший двигун engine.horsepower = 85 return engine def getBody(self): body = Body() # ну і тип кузова - хечбек body.shape = "hatchback" return body # код основної нашої програми: створюємо два автомобіля різних типів, # використовуючи Розпорядника (завод автомобілів), та різних Будівельників def main(): # створюємо два різних Будівельника jeepBuilder = JeepBuilder() nissanBuilder = NissanBuilder() # створюємо Розпорядника, управляючого усі виробництвом автомобілів director = Director() # спочатку створюємо джип print "Jeep" # даємо вказівку розпоряднику, що будемо зараз будувати Джипа director.setBuilder(jeepBuilder) jeep = director.getCar() jeep.specification() print "" # будуємо Нісан print "Nissan" director.setBuilder(nissanBuilder) nissan = director.getCar() nissan.specification() if __name__ == "__main__": main() |
Клас будівельника можна імплементувати кількома різними способами. Адже це абстрактний базовий клас, тому можна застосувати вбудовані можливості мови Python та скористатися метакласами і абстрактними методами:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Builder(metaclass=abc.ABCMeta): @abc.abstractmethod def getWheel(self): pass @abc.abstractmethod def getEngine(self): pass @abc.abstractmethod def getBody(self): pass |
Проте серед пітон програмістів метакласи та абстрактні методи не надто популярні. Саме тому в основному прикладі я навів простіший варіант класу Будівельника.
Як бачите, не зважаючи на те, який тип автомобіля ми будували, сам набір коду та команд залишився однаковим для Розпорядника. Нам просто треба було передати йому різні Будівельники, що отримати 2 різні типи автомобілів. В цьому і заключається основна суть даного патерна.
Переваги патерна
- Можливість контролювати процес створення складного продукта.
- Можливість отримання різних представлень продукта і при цьому розмежовувати та уніфікувати процес побудови об’єкта від його представлення.
Недоліки патерна
- ConcreteBuilder та продукт, який він створює тісно зв’язані між собою, тому при внесенні змін в клас продукта швидше за все прийдеться відповідним чином змінювати клас ConcreteBuilder.
***
От і все. Ще один породжуючий патерн оглянуто. Якщо у вас залишились питання – пишіть в коментах. Будемо разом розбирати.
А які у вас є приклади використання даного шаблону проектування коду?
Хочете більше дізнатись про веб-розробку та навчитись створювати веб-сайти використовуючи мову Python та веб-фреймворк Django? Гляньте дану пропозицію:
Будьмо дружніми!