Полювання на міфічний MVC. Огляд, повернення до першоджерел і про те як аналізувати і виводити шаблони самому

— Не розумію, чому люди так захоплюються цим Карузо? Недорікуватий, гугнив, співає — нічого не розбереш!
— А ви чули, як співає Карузо?
— Так, мені тут дещо з його репертуару Рабинович наспівав по телефону.
Детектив за матеріалами IT. Частина перша
Я усвідомлюю, що писати чергову статтю на тему Модель-Вид-Контролер це безглуздо і шкідливо для «карми». Проте з цим «паттерном» у мене надто особисті відносини – провалений проект, півроку життя і важкої роботи «в кошик».
Проект ми переписали, вже без MVC, просто керуючись принципами – код перестав бути схожий на клубок спагеті і скоротився наполовину (про це пізніше, в обіцяної статті про те, як ми застосовували «принципи» у своєму проекті). Але хотілося зрозуміти, що ж ми зробили не так, у чому була помилка? І протягом довгого часу вивчалося все, що містило абревіатуру MVC. До тих пір поки не зустрілися початкові роботи від творця – Трюгве Реенскауга…
І тоді все стало на свої місця. Виявилося, що фактично на основі принципів ми пере-винаходили «original MVC». А те, що часто підноситься як MVC, не має до нього ніякого відношення… втім також як і до гарної архітектури. І судячи з того скільки людей пише про неспроможності «класичного MVC», сперечається про нього і винаходить його різноманітні модифікації, не одні ми зіткнулися з цією проблемою.
Більше 30 років зібрані у MVC ідеї та рішення залишаються найбільш значущими для розробки користувальницьких інтерфейсів. Але як не дивно, незважаючи на існуючу плутанину і велика кількість суперечливих трактувань, розробники продовжують задовольнятися інформацією «з других рук», черпаючи знання про MVC з вікіпедії, невеликих статей в інтернеті і фреймворків для розробки веб-додатків. Найбільш «просунуті» читають Мартіна Фаулера. І чомусь майже ніхто не звертається до першоджерел. Ось цей пробіл і хотілося б заповнити. І заодно розвіяти деякі міфи.

Міфи: MVC створювався для мови SmallTalk
Концепція MVC була сформульована Трюгве Реенскаугом (Trygve Reenskaug) в результаті його роботи в Xerox PARC в 1978/79 роках. Як правило створення MVC пов'язують з мовою SmallTalk, але це не зовсім так. Насправді Реенскауг працював у групі, що займалася розробкою портативного комп'ютера "для дітей всіх віків" Dynabook під керівництвом Алана Кея (Alan Kay).
Щоб оцінити масштаб і революційність того проекту, потрібно мати на увазі що це були роки, коли для роботи з ЕОМ потрібно було вивчати багатосторінкові мануали і мати вчений ступінь. Задача, яку намагався вирішити Алан Кей, полягала в тому, щоб зблизити комп'ютер і рядового користувача, «зламати» розділяє стіну. Він хотів забезпечити користувача коштами, які були б гранично простими і зручними, але при цьому давали б можливість управляти комп'ютером і складними програмами.
Саме тоді/там закладалися основи графічного інтерфейсу, формувалося поняття "дружнього інтерфейсу". А також розроблявся мова SmallTalk, разом з концепцій об'єктно-орієнтованого програмування, щоб непідготовлений користувач міг розуміти і писати програми". Ось як описує побачене в Xerox PARC в 1979 році Стів Джобс – How Steve Jobs got the ideas of GUI from XEROX (from 6.30)
Проект тривав близько 10 років, групою дуже сильних розробників. Знайдені в результаті рішення, підходи, принципи та в області користувальницьких інтерфейсів, і в області об'єктно орієнтованого програмування і взагалі в розробці великих і складних комп'ютерних систем були в якійсь мірі проссумированы Реенскаугом і склали основу MVC. Так що MVC це дійсно насамперед сукупність направляючих архітектурних ідей. В SmallTalk-80 ці ідеї лише отримали свою першу значиму реалізацію. Причому зроблено це було вже після відходу Реенскауга з Xerox PARC і без його участі.
На жаль протягом довго часу про «реальний MVC» не було практично жодної доступної інформації. Перша серйозна публікація від творців з'явилася лише 10 років потому – "A Cookbook for Using the Model-View-Controller User Interface Paradigm in Smalltalk-80". Навіть Фаулер згадує, що він вивчав MVC по працюючої версії SmallTalk – "у мене був доступ до працюючої версії Smalltalk-80, щоб я міг вивчити MVC. Я не можу сказати, що це дослідження було ретельно, але воно дозволило мені зрозуміти деякі аспекти рішення, які інші описи пояснити не змогли".
Так що не дивно поява «міфів» та різноманітних трактувань. Проблема полягає в тому, що багато «вторинні» джерела описують MVC не тільки в спотвореному, але ще і в оманливо-спрощеному вигляді, як правило, у вигляді якоїсь формальної схеми.
В результаті багато хто дійсно вважають MVC схемою або паттерном (з-за чого постійно виникає питання – яка ж з безлічі існуючих схем «правильна» і чому їх так багато?). В більш просунутому варіанті MVC називають складовим паттерном, тобто комбінацією декількох патернів, які працюють спільно для реалізації складних додатків (тут зазвичай згадуються Observer, Strategy та Composite). І лише деякі розуміють, що MVC це насамперед набір архітектурних ідей/принципів/підходів, які можуть бути реалізовані різними способами з використанням різних шаблонів...
До останніх відноситься зокрема Мартін Фаулер. Ось що він пише: “MVC часто називають паттерном, але я не бачу особливої користі сприймати його як патерн, оскільки він включає в себе безліч різних ідей. Різні люди читають про MVC в різних джерелах і отримують від туди різні ідеї, але називають їх однаково — «MVC». Це призводить до великої плутанини і, крім того, служить джерелом непорозумінь і нерозуміння MVC, ніби люди дізнавалися про нього через «зіпсований телефон»… Я вже втратив рахунок скільки разів я бачив щось, що описується як MVC, яке їм не виявлялося."[ GUI Architectures]
Ризикну припустити, що одна з причин «зіпсованого телефону» полягає в тому, що більшість вторинних джерел «за кадром» залишають найголовніше – власне самі архітектурні ідеї, закладені в MVC його творцями, і ті завдання, які вони намагалися вирішити. Як раз все те, що дозволяє зрозуміти суть MVC і уникнути величезної кількості підводних каменів і помилок. Тому в даній статті я хочу розповісти про те, що зазвичай залишається «за кадром» – MVC з точки зору закладених у нього архітектурних принципів і ідей. Хоча схеми теж будуть. Вірніше з них і почнемо.
Але спочатку посилання. Вихідний доповідь Реенскауга – "The original MVC reports". Пізніше Реенскауг все це більш чітко сформулював і оформив у своїй подальшій роботі “The Model-View-Controller (MVC ). Its Past and Present". Можливо комусь буде цікава сторінка, де зібрані записи Ренскауга, що відносяться до того періоду, з його коментарями -MVC XEROX PARC 1978-79.
Вже згадувана перша публікація про MVC в мові SmallTalk-80 від розробників тільки в покращеній якості "A Description of the Model-View-Controller User Interface Paradigm in the Smalltalk-80 System" (Glenn Krasner і Stephen Pope). Гарним доповненням служить також стаття “Applications Programming in Smalltalk-80.How to use Model-View-Controller" (автор SteveBurbeck брав участь у розробці компілятора SmallTalk для IBM на основі Smalltalk-80, а також у розробці MacApp). Ну і якщо хтось хоче повного занурення – “Smalltalk-80. The Interactive Programming Environment" від знаменитої Адель Голдберг у дискусіях з якою Реенскаугом і створювалися терміни Model View Controller.
Схеми MVC
Для того, щоб стало зрозуміло, про що йдеться і в чому полягає проблема, давайте спочатку все ж розберемо найбільш типові схеми MVC. Це важливо, оскільки часто до схем не дається ніяких пояснень і до того-ж буває, що визначення запозичуються з одного місця, а схеми з іншого. В результаті можна зустріти однакові описи MVC з абсолютно різними діаграмами, що дуже заплутує.
Отже, незважаючи на те, що MVC трактується і зображується дуже по різному, у всьому цьому різноманітті можна виділити загальне «ядро». Спільним є те, що скрізь йдеться про якихось трьох частинах — Моделі, Вигляді і Контролері, які пов'язані між собою певним чином, а саме:
  1. Модель нічого не знає ні про Вигляді, ні про Контролері, що робить можливим її розробку і тестування як незалежної компонента. І це є головним моментом MVC.
  2. Вигляд відображає Модель. І значить, він якимось чином повинен отримувати з неї потрібні для відображення дані. Найбільш поширені наступні два варіанти: 1) Активний Вигляд, який знає про Моделі і сам бере з неї потрібні дані. 2) Пасивний Вид, якому дані поставляє Контролер. В цьому випадку Вид з Моделлю ніяк не пов'язаний.
    Видів може бути кілька — вони можуть по-різному відображати одні і ті ж дані, наприклад в вигляді таблиці або графіка, або ж відповідати за відображення різних частин даних з Моделі.
  3. Контролер є мабуть найбільш неоднозначним компонентом. Тим не менш загальним є те, що Контролер завжди знає про Моделі і може її змінювати (як правило в результаті дій користувача).
    А також він може здійснювати управління Видом/Видами (особливо якщо їх декілька) та відповідно знати про Види, але це не обов'язково.
Звідси ми отримуємо базові (максимально спрощені схеми двох найбільш поширених різновидів MVC. Перекресленою лінією позначена необов'язкова зв'язок Контролера з Видом.
Model View Controller
Ось так базова схема виглядає у Фаулера: "Основні зв'язки між Моделлю, Видом і Контролером. (Я називаю їх основними, тому що насправді Вигляд і Контролер можуть бути пов'язаними один з одним безпосередньо. Проте, розробники в основному не використовують цю зв'язок.)":
Model View Controller
Далі. Модель, як і Вигляд, теж може бути Пасивною або Активною. Пасивна Модель ніяк не впливає ні на Вигляд, ні на Контролер. В цьому випадку всі зміни Моделі фіксуються Контролером і він же відповідає за перемальовування Виду, коли це необхідно.
Але зазвичай, під MVC все таки мають на увазі варіант з Активною Моделлю.
«Активна Модель» сповіщає про те, що в ній відбулися зміни. І робить вона це за допомогою шаблону Спостерігач, розсилаючи повідомлення про зміни всім своїм передплатникам». «Активний Вид» підписується на ці повідомлення сам і таким чином знає коли потрібно заново вважати з моделі потрібні йому дані й оновитися. У випадку «Пасивного Виду», передплатником є Контролер, який потім вже оновлює Вигляд.
Шаблон Спостерігач дозволяє Моделі з однієї сторони інформуватимуть Вид або Контролер про те що в ній відбулися зміни, а з іншого фактично нічого про них «не знати» (крім того, що вони реалізують певний заданий інтерфейс «передплатника») і тим самим залишатися незалежною. Це називається слабким зв'язуванням і вважається другим ключовим моментом MVC.
Саме тому, коли говориться, що MVC це складовою шаблон, то в першу чергу в якості одного з його компонентів згадується патерн Спостерігач. На діаграмах слабке зв'язування прийнято малювати пунктирною стрілкою, але багато ігнорують це правило.
Таким чином, більш просунуті «схеми MVC» будуть виглядати так:
Model View Controller
Зауваження: зустрічаються автори, які в терміни Пасивна і Активна модель вкладають зовсім інший зміст. А саме те, що зазвичай прийнято називати Тонкої моделлю (модель містить виключно дані) і Товстої моделлю (повноцінна модель містить всю бізнес логіку програми).
Ну і останнє. Взагалі кажучи MVC, в будь-якій своїй різновиди, це насамперед шаблон для розробки додатків з інтерфейсом користувача і його головне призначення – забезпечити взаємодія програми з користувачем. Тому в повноцінній MVC схемою (явно або неявно) повинен бути присутнім користувач. І тут в основному зустрічаються два трактування:
  1. Користувач управляє додатком через Контролер, а Вигляд служить виключно для відображення інформації про Моделі, і користувач бачить лише
    Model View Controller
    Часто вказують/малюють лише те, що користувач діє на Контролер, а те що він бачить Вид опускається.
  2. Користувач взаємодіє тільки з Видом. Тобто Вигляд не тільки відображає Модель, але також приймає команди користувача і передає їх Контролеру. У цьому випадку між Видом і Контролером утворюється ще одна зв'язок: пряма (Вид знає про Контролері і безпосередньо передає інформацію) або, найчастіше, ослаблена (Вид просто розсилає інформацію про дії користувача всім зацікавленим замовникам а Контролер на цю розсилку підписується)
    Model View Controller
Зауваження: потрібно мати на увазі, що варіант з Пасивним Видом, коли Вид ніяк не пов'язаний з Моделлю і дані для відображення йому постачає Контролер, іноді називають MVC, а іноді виділяють у окрему різновид — MVP і тоді Контролер перейменовують Презентер.
Для ілюстрації всього вищесказаного кілька діаграм «з інтернету» (сподіваюся стало зрозуміліше чому вони такі різні):
Model View Controller
А тепер найголовніше — як застосовуються, що позначають і чому відповідає Модель Вигляд і Контролер при написанні програм?
Тут можна виділити два кардинально відрізняються підходу, в кожному з яких Модель, Вид і Контролер трактуються досить різним чином.
«Трирівневий MVC» від веб
Перший підхід йде з веб-програмування, де MVC отримав широке поширення, і тому в ньому максимально відбилися властиві веб-програмування риси. А саме, прив'язка до трирівневій архітектурі «клієнт–сервер–база даних» і переважання скриптових мов. В результаті компоненти MVC формально прив'язуються до трьох верствам архітектури і виходить що:
  1. Модель = База Даних
    Модель — це просто дані, якими оперує додаток
  2. Контролер =
    Контролер — це бізнес-логіка програми. Іноді ще говорять що контролер це центр обробки всіх запитів та прийняття рішень, а також проміжний шар забезпечує зв'язок моделі та подання.
  3. = Клієнт (як правило тонкий)
    Вид — це користувальницький інтерфейс. Причому інтерфейс в цьому випадку, як правило, розуміється в основному виключно як «дизайн», просто набір графічних елементів. Логіка ж роботи цього інтерфейсу, як і логіка роботи з даними, що виноситься в Контролер
<img src=«habrastorage.org/files/1ed/f60/74f/1edf6074f07940db9b303403c601a610.png» alt=«image» alt text"/>
Про неадекватність цього підходу написано вже так багато, що це увійшло навіть у вікіпедію (MVC. Найбільш часті помилки). Добре і детально виникаючі при цьому проблеми розглядаються у статті, яка стала свого роду класикою "M в MVC: чому моделі непоняты і недооцінені". Тому постараюся просто коротко підсумувати:
  1. Незалежність Моделі є головним у MVC. Якщо Модель тонка, тобто містить лише дані, то можливість її незалежної розробки має мало сенсу. Відповідно при такому підході втрачає сенс і сам MVC
  2. Вся бізнес логіка програми, тобто більша частина коду, зосереджується в Контролері і це при тому, що як раз Контролер є найбільш залежною частиною в MVC – у загальному випадку він залежить і від Моделі і від Виду. Взагалі кажучи в добре спроектованих додатках намагаються робити з точністю до навпаки – найбільш залежні частини повинні бути мінімальними, а не максимальними
  3. На практиці Контролеру у веб-додатку, зазвичай, відповідає один скрипт і винесення всієї бізнес-логіки в Контролер фактично означає ще й те, що більша частина програми опиняється в одному скрипті. Звідси і з'явився термін ТТУК — товстий тупий потворний контролер
  4. Оскільки, як правило, тонкої є не тільки Модель, але також і Вид (тупий Вигляд або тупий інтерфейс — Dump GUI), то, як наслідок, Контролер крім всієї бізнес-логіки додатка міститься також ще і логіка управління інтерфейсом. Тобто, замість поділу бізнес логіки і логіки подання при такому підході виходить їх змішання.
web MVC
Програма, звичайно, розбивається на безліч MVC, відповідних сторінках веб-додатки, і це рятує ситуацію, але, на жаль, не змінює суті. Проблема ця відома, ось непогана стаття — "RIA Architecture".
Типові помилки: змішання в Контролері бізнес-логіки і GUI-логіки
Гарна новина полягає в тому, що «веб-варіант MVC», всього кілька років тому колишній найпоширенішим, зараз активно здає позиції. Погано те, що він досі поширений, тільки тепер не в явному, а в замаскованому вигляді. Оскільки через фразу (цитую): "Модель це обмін даними з БД і т. п. Контролер логіка обробки цих даних та підготовка до View" зараз активно «мінусують», стали писати:
  • Модель — це дані і методи роботи з ними
  • Контролер — обробка дій користувача і вводиться ним інформації
Справа в тому, що в об'єктно-орієнтованому додатку немає даних, а є безліч об'єктів і кожен з них має якісь дані і методи роботи з ними. В тому числі і об'єктів доступу до бази даних (якщо вони є). Тому коли визначення Моделі починається зі слова «дані», то воно в суті має мало сенсу і нерідко в завуальованій формі увазі все той же самий доступ до бази даних. обробку ж дій користувача нерідко міститься левова частка бізнес логіки і в результаті раніше вся або майже вся логіка застосування часто виявляється в Контролері.
«Архітектурний MVC»
Другий підхід набагато ближче до першоджерел. Тому розглянемо його докладніше.
Мартін Фаулер абсолютно правий, коли говорить що MVC це не патерн, а набір архітектурних принципів і ідей, що використовуються при побудові користувальницьких інформаційних систем (як правило складних).
Архітектурні принципи ми постаралися зібрати і описати у статті "Створення архітектури програми або як проектувати табуретку". Якщо ж говорити гранично коротко, то суть полягає в наступному: складну систему потрібно розбивати на модулі. Причому декомпозицію бажано робити ієрархічно, а модулі, на які розбивається система, повинні бути, по можливості, незалежні або слабо пов'язані Low coupling). Чим слабкіше зв'язаність, тим легше писати/розуміти/розширювати/лагодити програму. Тому однією з основних задач при декомпозиції є мінімізація та ослаблення зв'язків між компонентами.
Давайте подивимося, як ці принципи застосовуються у MVC для створення первинної архітектури (декомпозиції) користувальницьких додатків. По суті в основі MVC лежать три досить прості ідеї:
«1» Відділення моделі предметної області (бізнес-логіки) програми від користувальницького інтерфейсу
Перша і основна ідея MVC полягає в тому, що будь користувальницьке додаток в першому наближенні можна розділити на два модулі — один з яких забезпечує основний функціонал програми, його бізнес логіку, а другий відповідає за взаємодію з користувачем:
MVC
Тим самим ми отримуємо можливість розробляти модель предметної області, що містить бізнес-логіку системи і складову функціональне ядро програми, не думаючи про те, як саме вона буде взаємодіяти з користувачем.
Завдання взаємодії з користувачем виноситься в окремий модуль – користувальницький інтерфейс і теж може вирішуватися відносно незалежно.
модель предметної області (Доменна Модель від англійського domain model) вважається Моделлю в «архітектурному MVC» (звідси і термін). Тому так важливо щоб вона була незалежною і могла незалежно розроблятися і тестуватися.
"Серцевиною ідеєю MVC, як і основною ідеєю для всіх наступних каркасів, є те, що я називаю «відокремлене подання» (Separated Presentation). Зміст відокремленого уявлення у тому, щоб провести чітку межу між доменними об'єктами, які відображають наш реальний світ, і об'єктами уявлення, якими є GUI-елементи на екрані. Доменні об'єкти повинні бути повністю незалежні і працювати без посилань на уявлення, вони повинні володіти можливістю підтримувати (support) множинні уявлення, можливо навіть одночасно. Цей підхід, до речі, так само був одним з важливих аспектів Unix-культури, що дозволяє навіть сьогодні працювати в безлічі додатків як через командний рядок, так і через графічний інтерфейс (одночасно)." — Фаулер
«2» Незалежність Моделі і синхронізація користувальницьких інтерфейсів за рахунок шаблону Спостерігач
Друга ключова ідея полягає в тому, що для того, щоб мати можливість розробляти Модель незалежно, необхідно послабити її залежність від користувальницького інтерфейсу. І робиться це, як вже згадувалося вище, за рахунок шаблону Спостерігач.
Модель розсилає повідомлення про зміни. Інтерфейс підписується на ці оповіщення і таким чином знає, коли потрібно заново вважати дані з моделі й оновитися. Завдяки цьому ми отримуємо практично незалежну Модель, яка нічого не знає про пов'язаних з нею користувацьких інтерфейсах, крім того що вони реалізують інтерфейс «спостерігача».
«3» Поділ Користувальницького Інтерфейсу на Вигляд і Контролер.
Третя ідея-це просто другий крок ієрархічної декомпозиції. Після первинного поділу програми на бізнес модель і інтерфейс, декомпозиція триває на наступному ієрархічному рівні і вже користувальницький інтерфейс, в свою чергу, ділиться на Вигляд і Контролер.
MVC
У мене склалося враження, що суть цього поділу мало хто розуміє і відповідно може пояснити. Зазвичай призводять лише стандартну обтічне формулювання, що Контролер якось реагує на дії користувача, а Вигляд відображає Модель (тому в більшості реалізацій саме Вид підписується на повідомлення про зміни Моделі. Хоча, як вже говорилося, передплатником може бути і Контролер, або Вид і Контролер разом).
Оскільки поділ користувальницького інтерфейсу на Вигляд і Контролер відноситься до другого рівня ієрархії, воно набагато менш значуще, ніж первинне розділення програми на доменну модель і інтерфейс. Дуже часто (особливо коли справа стосується простих віджетів) воно взагалі не робиться і використовується «спрощений MVC», в якому є тільки Модель і єдиний UI-компонент, що представляє собою об'єднаний ВидКонтроллер. Більш детально про це мова піде трохи пізніше.
«Архітектурний MVC» на перший погляд виглядає цілком розумно. Але як тільки ми спробуємо застосувати його не до навчального наприклад з трьох класів а до реальної програмі, то зіткнемося з цілим рядом проблем і питань, про які рідко пишуть, але які надзвичайно важливі. І стосуються вони не тільки користувальницького інтерфейсу, але і самої Моделі. Так що пропоную таки спробувати з ними розібратися і, нарешті, "послухати Карузо", тобто звернутися до першоджерел.
«Original MVC»: Реенскауг і SmallTalk-80
Ми звикли до того, що MVC майже завжди розглядається на прикладі створення якого-небудь простого графічного компонента, вся «бізнес-логіка» якого поміщається в один клас з даними і парою методів для їх зміни. Але що робити, коли йдеться про реальних додатках, ядро яких складається з багатьох взаємопов'язаних об'єктів працюють спільно?
В загальному випадку Модель це один об'єкт або безліч об'єктів? І чи насправді Модель «MVC-схемою» тотожна доменної моделі, що описує предметну область та бізнес-логіку програми?
Те, що Модель реалізує шаблон Спостерігач явно вказує на те, що Модель це саме один об'єкт. На це ж вказує і те, що Вид і Контролер повинні знати про Моделі (для того щоб брати з неї дані і вносити зміни) і, отже, вони повинні містити посилання на неї. Але тоді, якщо вважати, що під Моделлю розуміється доменна модель, ми знову приходимо до того що всі ядро програми опиняється в одному об'єкті. Тільки тепер замість товстого потворного Контролера, у нас з'являється товста Модель. Товста Модель звичайно краще, оскільки вона незалежна і в ній, принаймні, не змішується бізнес логіка з логікою GUI, але все одно таке рішення складно віднести до гарної архітектури.
Залишається другий варіант — це Модель безліч доменних об'єктів, спільно реалізують бізнес-логіку. Це припущення підтверджує і сам Реенскауг: "A model could be a single object (rather uninteresting), or it could be some structure of objects." Але тоді залишається відкритим питання – хто реалізує шаблон Спостерігач, звідки бере дані Вид, куди передає команди користувача Контролер?
І ось тут нерідко зустрічається спроба обдурити самих себе шляхом приблизно наступного міркування: "нехай Модель це безліч доменних об'єктів, але… серед цієї множини є в тому числі і «об'єкт з даними», ось він-то і буде реалізовувати шаблон Спостерігач, а також служити джерелом даних для Виду." Цей прийом можна назвати «Модель в Моделі». І по суті, це ще один «завуальований» варіант того, що «Модель це дані».
Тут можна сказати лише одне: архітектура, в якій один модуль (Вид або Контролер), повинен «лізти» всередину іншого модуля (доменної моделі) і там шукати для себе дані або об'єкти для зміни дуже недобре «пахне». Виходить що Вид і Контролер залежать від деталей реалізації доменної моделі, і якщо структура цієї самої моделі зміниться, то доведеться переробляти весь користувальницький інтерфейс.
Для того ж, щоб зрозуміти «як повинно бути» пропоную знову звернеться до «принципів». Коли йшлося про те, що систему треба розбивати на модулі, слабо пов'язані один з одним, ми не згадали головне правило, що дозволяє досягнути цієї самої слабкої зв'язаності. А саме – модулі один для одного повинні бути «чорними ящиками». Ні при яких умовах один модуль не повинен звертатися до об'єктів іншого модуля безпосередньо і що знати про його внутрішній структурі. Модулі повинні взаємодіяти один з одним лише на рівні абстрактних інтерфейсів (Dependency Inversion Principle). А реалізує інтерфейс модуля як правило спеціальний об'єкт — Фасад.
І якщо пошукати які ж патерни дозволяють добитися слабкою зв'язаності, то на першому місці буде знаходиться саме патерн Фасад, і тільки потім Спостерігач і тд.
Ну а тепер схема з доповіді Трюгве Реенскауга:
MVC Reenskaug
Пояснення: Оскільки в часи створення MVC інтерфейси комп'ютерних програм були в основному текстовими, тобто, по суті являли собою найпростіший вид редактора, то замість терміна «Інтерфейс», який з'явився пізніше, Трюгве Реенскауг використовує термін «Editor (редактор).
Таким чином, ключова ідея MVC дійсно полягає в тому, що клієнтська програма поділяється на два модулі – один з яких моделює предметну область і реалізує бізнес логіку (доменна модель), а другий відповідає за взаємодію з користувачем (користувальницький інтерфейс). Але при цьому Модель в «MVC схемою» зовсім не тотожна доменної моделі (яка може бути як завгодно складною і складатися з безлічі об'єктів), а є лише її інтерфейсом і фасадом.
Так що ні Вигляду ні Контролер зрозуміло не повинні знати про те, як влаштований модуль предметної області (доменна модель), де і в якому форматі там зберігаються дані, і як саме здійснюється управління. Вони взаємодіють лише з інтерфейсом і реалізують його об'єктом-фасадом, який надає всі потрібні дані в потрібному форматі і зручний набір високорівневих команд для управління підсистемою, а також реалізує шаблон Спостерігач, для сповіщення про значущих зміни в підсистемі. І якщо ми захочемо змінити базу даних, використовувати хмара, або взагалі збирати потрібні нам дані з різних джерел в мережі… якщо внесемо будь-які зміни в бізнес логіку програми, але при цьому залишимо незмінним інтерфейс-фасад, то ні Вид, ні Контролер це ніяк не торкнеться. Ми маємо архітектуру стійку до змін.
І якщо вже малювати схему MVC, то виглядати вона повинна наступним чином:
MVC
Давайте розглянемо цю схему докладніше. Традиційно в клієнт-серверних додатках головним вважається сервер. Він надає послуги/сервіси і вирішує в якому вигляді це має бути реалізовано. Відповідно інтерфейс і фасад, як правило, визначаються з точки зору сервера. А клієнти під цей заданий формат підлаштовуються.
На практиці ж більш адекватної виявляється не сервер-орієнтована архітектура, а клієнт-орієнтована. В ній фокус з сервера зміщується в сторону клієнта і інтерфейс, вірніше інтерфейси (і фасад або фасади), визначаються виходячи із потреб клієнтів. Замість Наданого Інтерфейсу (Provided Interface) використовуються Необхідні Інтерфейси (RequiredInterface).
RequiredInterface
Конкретні реалізації можуть змінюватись, але це не суть важливо
RequiredInterface
Клієнт орієнтований підхід набагато краще відповідає Принципом поділу інтерфейсів (Interface Segregation Principle) оскільки в ньому замість єдиного для всіх толстого ProvidedInterface використовується безліч тонких RequiredInterface.
Якщо я не помиляюся, саме такий підхід використовується в архітектурі микросервисов. Там для взаємодії з безліччю сервісів введено поняття шлюзу, який є ні чим іншим як фасадом — “An API Gateway is a server that is the single entry point into the system. It is similar to the Facade pattern from object-oriented design. The API Gateway encapsulates the internal system architecture and provides an API that is tailored to each client. "Building Microservices: Using an API Gateway.
Причому шлюз цей "замість того, щоб забезпечувати загальний єдиний для всіх API, надає різні API для кожного клієнта (Rather than provide a one-size-fits all style API, the API gateway can expose a different API for each client. For example, the Netflix API gateway runs client-specific adapter code that provides each client with an API that's best suited to it's requirements)" API Gateway.
Як ми побачимо далі клієнт-орієнтований підхід застосовувався також і в SmallTalk-80. Але спочатку давайте просто пере-малюємо схему MVC з урахуванням вищесказаного:
MVC
Дивимося на фасад… Ось він той самий клей (glue), об'єкт посередник, проксі, фільтр, адаптер… зв'язує між собою доменну модель і користувальницький інтерфейс і поставляє потрібні дані в потрібному/зручному форматі.
Дивно те, що крім Реенскауга про це майже ніхто не пише. Хоча деякі пере-відкривають цю ідею самостійно (приклад можна подивитися тут або тут розділ "Interface-Based Programming Techniques").
Особливо добре тема Моделей-інтерфейсів розкрита у статті одного з JavaGuru — Advanced MVC Patterns. Автор підкреслює, що Моделі це не дані, а виключно інтерфейси об'єкти-посередники/фільтри (Models as Proxies, Models as Filters), що забезпечують зручний доступ до даних, які можуть перебувати де завгодно – на різних машинах, в різних форматах: “Про що більшість програмістів не думає, так це про те, що моделі є всього лише інтерфейсами. Вони не повинні містити ніяких даних!.. Моделі-посередники розширюють обсяги і дозволяють використовувати вже існуючі дані де б вони не знаходилися".
З-за того, що фасад, присутній у original MVC, був «загублений», то його роль часто бере на себе Контролер. Звідси і виникають уявлення що Контролер знаходиться «між Моделлю і Видом», служить клеєм між ними і забезпечує потрібні Увазі дані.
На форумах нерідко зустрічається питання — "Чим контролер відрізняється від фасаду?". Не дивлячись на наївність це питання цілком закономірний і на нього складно дати розумну відповідь оскільки у багатьох MVC фреймворках Контролер насправді фактично є фасадом «Фронт-Контролер».
Чим погано таке рішення? Якщо воно грамотно реалізовано, то нічим. Але це в теорії. А на практиці нерідко відбувається плутанина концепцій і понять і в результаті Фронт-Контролер з одного боку зловживає своїми повноваженнями і замість делегування команд починає включати в себе реалізацію бізнес логіки. А з іншого – продовжує одночасно виконувати функції користувальницького інтерфейсу і в результаті в ньому відбувається вже згадуване змішання «бізнес логіки» і «GUI логіки» (що власне і робить його код схожим на величезне звалище).
Думаю, що прийшов час перейти до Smalltalk. Smalltalk-80 створювався дуже талановитими людьми. З документацією в ньому дійсно були проблеми (тим більше що «шаблонів проектування» тоді ще не існувало) але от з реалізацією в основному все було добре і користувальницькі інтерфейси, звичайно ж, не взаємодіяли з доменної моделлю безпосередньо.
Між інтерфейсом і доменної моделлю (об'єктами мови SmallTalk) завжди розташовувався якийсь проміжний клас/об'єкт, який забезпечував зручний інтегральний доступ до доменним об'єктів їх даними і методами. Ось ці-то проміжні об'єкти (по суті виконують роль фасадів) і були насправді Моделями в SmallTalk-80.
Наприклад, для роботи з кодом Smalltalk використовувалися наступні GUI інтерфейси: Inspector, Browser, Workspace…
smalltalk-80 MVC
Ось що пише про їх пристрої Glenn Krasner:
"Inspector в системі складається з двох видів. ListView відображає список змінних (ліворуч), а TextView показує значення виділеної змінної (праворуч)… Моделлю для цих видів служить екземпляр класу «Inspector»… Окремий клас «Inspector є посередником або фільтром для того щоб забезпечувати доступ до будь-якого властивості будь-якого об'єкта. Використання проміжних об'єктів між View і "actual" models є типовим способом ізолювати поведінка відображення від моделі додатки...
Як і у випадку Inspector, проміжні об'єкти використовувалися також в якості моделей для системних браузерів. Екземпляр класу «Browser» є моделлю-посередником для кожного системного браузера..."
Примітка: назва класу-посередника, що описує проміжний об'єкт-фасад, зазвичай збігалася з назвою відображає його віджета. У Inspector проміжна модель так і називалася «Inspector», а у Browser відповідно – «Browser».
У разі Workspace, який був одним з найпростіших інтерфейсів "моделлю служив примірник StringHolder, який просто надавав текст, тобто рядок з інформацією про форматування".
smalltalk-80 MVC
У кінці своєї статті Krasner призводить список використаних в SmallTalk Моделей (спадкоємців базового класу Model): StringHolder, Browser, Inspector, FileModel, Icon… А також зазначає, що "the models were almost always some sort of filter class".
Пізніше в VisualWorks Smalltalk ідея проміжних Holder-ів була розвинена і реалізована повною мірою. Там для доступу до кожної змінної, що належить доменним об'єктів, використовується свій інтерфейс і фасад – ValueModel і ValueHolder. І, як не важко здогадатися, саме ValueModel реалізує шаблон Спостерігач, сповіщаючи GUI про події, що відбуваються «у домені» зміни.
smalltalk Value-Model MVC
Типові помилки: звернення до доменним об'єктів безпосередньо
Оскільки на практиці в будь-якому скільки-небудь серйозному додатку складно обійтися без фасадів, то не дивно що в багатьох фреймворках і "модифікаціях MVC" аналоги фасаду чи об'єкта-посередника між GUI і доменної моделлю пере-винаходяться під різними іменами. Крім Front-Controller тут можна згадати також ApplicationModel, ViewModel (докладніше див. дискусію Model-ModelView-Controller і Proxy-model.
З-за того, що розробники не завжди добре розуміють, що стоїть за всіма цими «моделями», а самі моделі звикли сприймати як дані, а не інтерфейс, то це стає джерелом ще однією досить поширеною і ресурсномісткою помилки. Замість того щоб потрібним чином лише інтерпретувати і адаптувати наявні доменні дані за допомогою моделей-посередників їх починають копіювати в ці моделі-посередники.
Наприклад, ValueHolder це, як правило, лише обгортка навколо вже існуючої доменної змінної, він не повинен містити дані, він містить посилання на дані. Ось що пишуть: "ValueModel does not need to actually store the value because it is already being stored by another model" (Understanding and Using ValueModels).
А ось цитата зі статті Advanced MVC Patterns: "Одна з найпоширеніших помилок, яку роблять люди коли використовують Swing компоненти, полягає в копіюванні даних в моделі цих Swing компонент. Правильний спосіб полягає в тому, щоб використовувати вже існуючі дані, адаптуючи їх за допомогою фільтра… Запам'ятайте: ніколи не копіюйте дані які можна просто інтерпретувати!".
Розглянемо наступний простий приклад. Якщо наслідувати інтернет радам і для "додавання елементів в список" використовувати код подібний цього (взято з StackOverflow і Adding and Removing an Item in a JList), то буде відбуватися як раз те саме копіювати даних у модель списку:
Object[] items; // Доменний об'єкт

DefaultListModel model = new DefaultListModel();
JList list = new JList(model);
for (int i = 0; i < items.length; i++){
// КОПІЮВАННЯ доменних даних у модель списку!
model.addElement(items[i]); 
}

Правильніше, звичайно ж, використовувати дані масиву просто обернувши їх на інтерфейс ListModel (тим більше що для цих цілей створено вже майже готова AbstractListModel):
// створюємо фасад-адаптер до доменним даними,
// який не просто інтерпретує їх потрібним чином
ListModel model = new AbstractListModel() {
public int getSize() {return items.length;}
public Object getElementAt(int index) {return items[index];}
};
// передаємо створений фасад списком в якості моделі
JList list = new JList(model);

І якщо треба об'єднати дані, відфільтрувати або перетворити яким-небудь чином, то абсолютно не потрібні проміжні масиви. Все робиться безпосередньо в моделі-фасаді
Object[] items1; Object[] items2; // Доменні об'єкти

// модель-фасад яка об'єднує масиви
ListModel model = new AbstractListModel() {
public int getSize() { return items1.length + items2.length;}
public Object getElementAt(int index) {
return index<items1.length ? items1[index] : items2[index-items1.length];
}
};
JList list = new JList(model);

У випадку невеликих статичних масивів переваги не очевидні. Але в загальному випадку такий підхід дозволяє не тільки уникати копіювання, але насамперед захищає від проблем пов'язаних з розсинхронізацією даних (якщо не полінуватися прописати методи сповіщають слухачів про зміни).
Ну а якщо хочеться стислості, то тоді вже краще так:
JList list = new JList(items);

В цьому випадку Джава сама зробить обгортку-адаптер замість копіювання.
Типові помилки: копіювання доменних даних в моделі GUI-компонент
 
Ну і нарешті ми може розвіяти головний міф, який є джерелом найбільшої кількості проблем і помилок.
Міфи: Модель «MVC схемою» тотожна доменної моделі і даними
Плутанина виникає через те, що одне і те ж слово «Модель» використовується в різних контекстах. Коли мова йде про декомпозиції та відділенні бізнес-логіки користувальницького інтерфейсу, то під Моделлю дійсно розуміється саме доменна модель, що містить дані і логіку роботи з ними і забезпечує основний функціонал програми.
Але в контексті шаблонів і схем Модель це насамперед інтерфейс і реалізує його об'єкт-посередник (фасад, адаптер, проксі) забезпечують зручний і безпечний доступ до доменним даними, які можуть перебувати де завгодно. Реенскауг так і писав: "object model with a façade that reflects the user's mental model".
Коли MVC подається виключно як «схема», то наявність «проміжних моделей» здається складним і заплутаним. З'являються питання ("Чим ці моделі відрізняються один від одного?", «Як їх правильно використовувати?»), неоднозначні трактування та безліч можливостей зробити помилку.
Але якщо розуміти закладені в MVC архітектурні ідеї, то все стає зрозуміло: користувальницький інтерфейс не має права звертатися до об'єктів доменної моделі напряму. А значить між доменної моделлю і інтерфейсом повинно знаходитися фасад/посередник/адаптер… і взаємодіяти користувальницький інтерфейс (Вид і Контролер) може тільки з ним. Можливостей зробити помилку – нуль.
І за великим рахунком стає все одно яким терміном цей об'єкт-посередник називається і яка саме Model-View-Whatever різновид MVC використовується… Починаєш бачити: яке завдання вирішується, за допомогою яких шаблонів і те, наскільки добре або погано це робиться
В принципі на цьому статтю можна і закінчити. Як вже згадувалося поділ користувальницького інтерфейсу на Вигляд і Контролер є найменш значущим, навіть допоміжним моментом. Але з іншого боку користувальницький інтерфейс присутній у кожному вами програмі та мати уявлення про підходи та ідеї напрацьованих в цій області часто буває корисно. До того ж саме навколо Контролера ведуться основні суперечки. Тому, якщо є ті, кому цікаво і не набридло», то пишіть і я викладу другу частину повністю присвячену саме цій темі.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.