Перший патерн, який ми з вами розглянемо у даній серії буде Абстрактна Фабрика. Англійською мовою його назва виглядає наступним чином: Abstract Factory.

Abstract Factory

UML діаграма патерна Абстракна Фабрика

В даній статті ми оглянемо:

  • що являє собою даний патерн, та для чого він потрібен;
  • розглянемо приклад: Войнушки;
  • Плюси та Мінуси даного патерна;
  • нюанси використання даного патерна в мові Python.

Отже:

Означення

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

Щось зрозуміли? Я – ні. Коли вперше прочитав дане непросте речення.

Проте після низки загуглених сторінок з прикладами на різних мовах програмування, серед англійських блогів та журналів, зрозумів, в чому суть. А ще зрозумів, що насправді давно використовую даний патерн у своїй щоденній роботі (правда у трохи іншій формі), просто до певного часу не свідомо і дійшов до нього читаючи чужий код.

Приклад: Войнушки

Розберемо даний патерн на старому доброму наглядному прикладі.

Уявимо, що воюють дві видумані (щоб уникнути аналогій та паралелей 😉 країни: Ліліпути та Велетні. В кожної країни є по три роди військ: піхота, повітряні війська та морські війська. Відповідно у кожної країни є військові наступних типів: піхотинець, льотчик та моряк. Кожен рід військ кожної з країн воює між собою. Тобто піхотинець із піхотинцем, моряк з моряком і льотчик з льотчиком.

А тепер давайте спробуємо окреслити необхідний мінімум коду та класів, щоб описати ці воєнізовані дії програмно.

Для кожного типу бійця та своєї країни буде визначений свій клас. На мові Python це вигладатиме наступним чином:

Вище описані шість Python класів по 3 на кожну країну Ліліпутію та країну Велетнів. Кожен із класів має методі ініціалізації, який встановлює ім’я бійцю. Також є метод attack, який дозволяє бійцю атакувати іншого бійця, того ж роду військ.

Тепер давайте глянемо як виглядатиме війна:

Загалом непогано. Але…

Нам довелось використовувати явно імена класів в коді нашої війни. Для 3-х типів військ і двох країн це не проблема. Але, якщо матимемо 20 родів військ і 10 країн, які воюють між собою, тоді отримаємо масу проблем із підтримкою такого кучерявого коду.

Тому давайте спробуємо написати Абстрактну Фабрику, яка перебере на себе цю нудну справу – Створення нового об’єкта для сімейства (в нашому випадку це країна). Абстрактна Фабрика використовуватиметься для створення вояків для обидвох країн. Така собі заготовка для заводу виробництва солдат. Давайте спочатку кінцевий код оглянемо, а тоді більш детально розжуємо усе:

Клас  ‘AbstractSoldatFactory’ якраз і є Абстрактною Фабрикою. Такою собі заготовкою (інтерфейсом), з якої вже можна допиляти конкретний завод з виготовлення різноманітних солдат для тої чи іншої країни. Один завод може виготовляти солдат лише для однієї країни.

Інтерфейс – це свого роду домовленість, декларація про певний набір атрибутів та методів, який клас (той що імплементує даний інтерфейс) повинен реалізовувати.

В мові Python інтерфейсів немає вбудованих в інтерпретатор. Вони прийшли як додаток із світу Zope, у вигляді пакету zope.interfaces. І навіть, якщо використовувати даний пакет, все одно інтерфейс залишається не обов’язковим для класу і може використовуватись в наступних цілях:

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

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

Таким чином на війні нам не треба переживати про те звідки брати солдат, як називаються їхні класи і т.д. Перед війною ми гарно підготувались – створили заводити на базі класів абстрактної та конкретних фабрик, і вже на війні з легкістю створюємо солдатів для обидвох країн використовуючи методи ‘create<Тип Солдата>’ кожної із фабрик.

Отже, основною задачею Абстрактої Фабрики є лише декларація інтерфейса (домовленості) для створення об’єктів того чи іншого сімейства. А вже конкретні імплементації абстрактної фабрики (у нашому випадку це LiliputFactory та VelikanFactory) займаються самим виробництвом (створенням) об’єктів згідно домовленостей Абстрактної Фабрики.

Плюси патерна та коли використовувати

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

Варта використовувати при розробці кросплатформенного коду. Також коли в майбутньому прийдеться переключатись між різними сімействами об’єктів. Абстрактна Фабрика дозволить зробити це одним махом.

Породжувальні Патерни конкурують між собою. У наступних статтях даної серії, коли розглядатимемо інші породжувальні патерни, детальніше розглядатимемо, який патерн і коли краще працює.

Недоліки патерна

Трудоємко додавати новий тип об’єкта у сімейство об’єктів. Для цього потрібно додати опис до Абстракної Фабрики (інтерфейсу), а потім пройтись по кожній із специфічних фабрик та додати до кожної із них новий факторі метод.

Щоб цього уникнути, можна мати лише один факторі метод (напр. універсальний createObject), який прийматиме ідентифікатор необхідного для створення типу об’єкта. Тут виникає нова проблемка, але вникати в деталі поки далі не будемо.

Доцільність в мові програмування Python

Взагалі для Python програмістів деякі із Патернів Програмування звучать досить подібно один з іншим. І навіть деякі з них зазвичай непотрібні. В основному тому, що оригінальні патерни були в першу чергу придумані для мов типу C++ для того, щоб обходити певні обмеження мови. Мова Python даних обмежень не має, саме тому частина патернів може виглядати при програмуванні на цій мові трохи надуманою.

Конкретно в мові Пітон Абстрактна Фабрика не надто поширена в її оригінальному вигляді. Навіть не зважаючи на введення статичних методів класу, метакласів (модуля abs – abstract base class – абстрактний базовий клас) все одно широкої популярності даний патерн серед програмістів не набув. Думаю ще однією причиною малої популярності даного патерну є відсутність, як таких, обов’язкових інтерфейсів у мові Python, а також динамічність мови.

Спочатку приклад коду з використанням Python примочок для абстрактних класів:

А тепер приклад як зазвичай реалізовують потребу створення сімейств об’єктів пітонщики. Пітон є динамічно типізованою мовою і нам не потрібен абстрактний клас, щоб передавати класи функціям в якості аргументів. Зауважте на скільки поменшало коду:

Оскільки Абстрактна Фабрика не виглядає надто природньою в інтерпретації Пітона, відповідно і реалізації є найрізноманітніші починаючи від порожніх методів і закінчуючи ‘raise NotImplementedError’ методами, щоб написати абстрактний клас із функціями.

***

Надіюсь вдалось дохідливо пояснити суть Абстрактної Фабрики та для чого вона може вам знадобитися, незважаючи на дивний приклад з ліліпутами та велетнями льотчиками 😉 Через тиждень розглянемо наступний патерн: Будівельник (Builder).

Чекаю ваших коментарів та критичних зауважень!

А як на вашій мовій можна наглядно пояснити патерн Абстрактної Фабрики?

Хочете більше дізнатись про веб-розробку та навчитись створювати веб-сайти використовуючи мову Python та веб-фреймворк Django? Гляньте дану пропозицію: