JavaRush /Курси /Architecture & Logic /Патерни проектування

Патерни проектування

Architecture & Logic
Рівень 1 , Лекція 0
Відкрита

Знайомство з патернами

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

Тому, щоб знизити складність програми, взаємодію об'єктів намагаються стандартизувати. І в цьому програмісту дуже допомагають шаблони проектування або патерни проектування. Від англійського design pattern.

Важливо! Ми звикли, що під словом «дизайн» зазвичай мають на увазі графічний дизайн, але в англійській мові це не так. Англійське слово design за змістом ближче до слова «проектування» та/або «будова». Наприклад, дизайн двигуна — це не його зовнішній вигляд, а його внутрішня будова.

Тому design pattern — це саме патерн/шаблон проектування. Рекомендую взагалі перестати вживати слово «дизайн» у значенні «зовнішній вигляд». Ви — майбутній Software Engineer, і для вас дизайн — це саме проектування.

То що ж таке цей design pattern? Насамперед патерн проектування — це стандартне рішення стандартної проблеми. Хороше, ефективне і рішення, випробуване часом.

Припустимо, вам доручили спроектувати велосипед. Ви можете зробити йому два колеса, три або навіть п'ять. Так, до речі, на світанку проектування і було. Але перевірений часом підхід — два колеса. А до нинішнього очевидного підходу йшли через біль та помилки:

Еволюція велосипеда

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

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

Історія появи патернів проектування

Ще у 70-ті роки програмісти зіткнулися з необхідністю розробляти великі програми, над якими мали працювати цілі команди розробників. Було випробувано різні методи організації роботи, але найбільше на розробку вплинула будівельна сфера.

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

І, як ви вже здогадуєтеся, ідею design pattern також запозичили зі сфери будівництва. Концепцію патернів вперше описав Крістофер Александер у книзі «Мова шаблонів. Міста. Будівлі. Будівництво». У цій книзі для опису процесів проектування міст була використана спеціальна мова — патерни.

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

Тому не дивно, що у 1994 році вийшла книга «Прийоми об'єктно-орієнтованого проектування. Патерни проектування», до якої увійшли 23 патерни, що вирішують різні проблеми об'єктно-орієнтованого дизайну.

Книгу написали 4 автори: Еріх Гамма, Річард Гелм, Ральф Джонсон та Джон Вліссідес. Назва книги була занадто довгою, щоб хтось міг її запам'ятати. Тому незабаром усі почали називати її «book by the gang of four», тобто «книга від банди чотирьох», а згодом і зовсім «GoF book».

З того часу було відкрито ще багато інших патернів проектування. «Патерновий» підхід став популярним у всіх галузях програмування, тому зараз можна зустріти різноманітні патерни і за межами об'єктного проектування.

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

Python-way: чому не можна просто скопіювати код із Java/C++?

Класична книга «Банди чотирьох» (GoF) — це біблія проектування. Але є нюанс: вона була написана у 1994 році з акцентом на мови C++ та Smalltalk. Багато проблем, які ці патерни вирішували в C++, у Python або відсутні, або вирішуються штатними засобами мови.

Якщо ви спробуєте знайти в Інтернеті приклади патернів на Python, ви часто натрапите на туторіали, де автори механічно переписують Java-код на Python. Там будуть абстрактні фабрики фабрик, інтерфейси, які нічого не роблять, і класи, єдине завдання яких — зберігати одну функцію.

Важливо! Сліпе копіювання класичних реалізацій у Python — це антипатерн. Такий код називають «Java-style Python». Він працює, але його складно читати, тестувати та підтримувати.

Python — мова з динамічною типізацією, де функції — це об'єкти першого класу, а модулі працюють як готові простори імен. Те, що в C++ вимагало побудови складної ієрархії класів, у Python часто вирішується елегантніше:

  • Singleton (Одинак) у C++ це складний клас із контролем єдиного екземпляра. У Python будь-який імпортований модуль — це вже Singleton за дизайном мови. Нам не потрібно писати class Singleton, ми просто створюємо об'єкт у файлі settings.py.
  • Strategy (Стратегія) у старих мовах для цього потрібно створювати інтерфейс і купу класів-нащадків. У Python ми можемо просто передати функцію як аргумент в іншу функцію.
  • Decorator (Декоратор) у Python це взагалі вбудований синтаксис мови @login_required, а не просто архітектурний прийом.
  • Builder (Будівельник) часто замінюється грамотним використанням іменованих аргументів kwargs у конструкторі класу та значеннями за замовчуванням.

Тому в цьому курсі ми будемо вчитися не просто «зубрити схеми», а розуміти суть проблеми. Якщо проблему можна вирішити стандартними засобами Python (списковими включеннями, декораторами, контекстними менеджерами), ми будемо робити саме так. А до «важкої артилерії» класичних патернів будемо вдаватися там, де це дійсно необхідно для архітектури.

Навіщо знати патерни і якими вони бувають

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

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

Можна забити цвях гайковим ключем, але навіщо? Можна навіть і дрилем, якщо дуже постаратися. Але хороше усвідомлене володіння інструментом якраз і відрізняє професіонала від любителя. І професіонал знає, що головна фішка дриля зовсім не в цьому. Отже, навіщо ж знати патерни?

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

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

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

Найнижчому рівню та простоті відповідають ідіоми. Вони не універсальні, оскільки застосовні лише в межах однієї мови програмування.

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

Але головне — патерни відрізняються призначенням. Патерни, з якими ми познайомимося, можна розбити на три основні групи:

  • Породжувальні патерни (Creational) — дбають про гнучке створення об'єктів без внесення в програму зайвих залежностей. Вони відповідають на запитання: «Як створити об'єкт, щоб не заплутати код?».
  • Структурні патерни (Structural) — показують різні способи побудови зв'язків між об'єктами. Відповідають на запитання: «Як зібрати класи в єдину робочу структуру?».
  • Поведінкові патерни (Behavioral) — дбають про ефективну комунікацію між об'єктами. Відповідають на запитання: «Хто і за що відповідає, і як об'єкти спілкуються?».

Знайомство з UML

Почнемо з 23 патернів із книги «Банди чотирьох» (GoF). Самі патерни не прив'язані до конкретної мови, тому для їх опису використовується стандарт UML (Unified Modeling Language).

У Python ми рідко малюємо детальні UML-діаграми перед написанням коду (ми віддаємо перевагу написанню прототипів), але читати їх потрібно вміти. Це «креслення», на яких показано, як класи залежать один від одного.

Типи зв'язків в UML

classDiagram
        %% 1. Композиція (Сильний зв'язок)
        Order *-- OrderItem : Композиція
        note for Order "Ціле. Якщо видалити Замовлення, рядки також зникнуть."

        %% 2. Агрегація (Слабкий зв'язок)
        Department o-- User : Агрегація
        note for Department "Ціле. Якщо закрити Відділ, Співробітники залишаються."

        %% 3. Успадкування (Узагальнення)
        User <|-- Admin : Успадкування (Is-A)
        class Admin{
            +delete_user()
        }

        %% 4. Залежність
        ReportGenerator ..> Order : Залежність (Uses)
        note for ReportGenerator "Генератор тимчасово використовує Замовлення, щоб створити PDF."
  

Розберемо ці стрілки докладніше, від сильних зв'язків до слабких:

  • Успадкування (суцільна лінія з трикутником) — класичне ООП. Admin є User. Він отримує всі його властивості та методи.
  • Композиція (чорний ромб) — зв'язок «Частина-Ціле» (нерозривний). OrderItem не має сенсу без Order. У Django це часто реалізується через on_delete=models.CASCADE.
  • Агрегація (білий ромб): зв'язок «Частина-Ціле» (вільний). User працює в Department. Але якщо відділ розформують, користувач не видаляється з бази, він просто змінює відділ. У Django це on_delete=models.SET_NULL.
  • Залежність (пунктирна стрілка) — найслабший зв'язок. Клас ReportGenerator не зберігає всередині себе замовлення. Він просто отримує його як аргумент методу, бере дані та «забуває».

Повний список патернів

Види патернів позначатимемо різними кольорами та літерами:

B — поведінкові (behavioral);

C — породжувальні (creational);

S — структурні (structural).

І нарешті список із 23-х патернів проектування:

C — Абстрактна фабрика

S — Адаптер

S — Міст

C — Будівельник

B — Ланцюжок відповідальності

B — Команда

S — Компонувальник

S — Декоратор

S — Фасад

C — Фабричний метод

S — Пристосуванець

B — Інтерпретатор

B — Ітератор

B — Посередник

B — Хранитель

C — Прототип

S — Проксі

B — Спостерігач

C — Одинак

B — Стан

B — Стратегія

B — Шаблонний метод

B — Відвідувач

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ