Разработка

индекс
203,40

Правило разделения — не вздумайте злоупотреблять!

Комментарий к статье о «правилах разделения» превратился в небольшую отдельную статью, которая вновь доказывает, что разработка любого приложения сложнее Hello World требует обоснованных решений и творческого подхода, иногда опровергающего эти самые решения.



В своей статье, Рауф приводит MVC, как очень правильный шаблон проектирования использующий разделение ответственности. Я бы с этим не согласился, а первое хорошее шаблонное разделение, которое приходит мне в голову — Observer (наблюдатель).

На мой вгляд MVC, как и MVP (Model View Presenter) шаблоны мощные, но достаточно размытые. Это значит, что никогда нельзя провести четкую грань между M, V и C/P — т.е. без привязки к конкретному проекту никогда нельзя сказать, где кончается контроллер и начинается представление, или какой код мы будем писать в модели, а какой в контроллере.

Насчет веб/клиент-сервер/server-side+JS+AJAX я бы тоже не очень согласился с тем, что надо очень жестко делить логику и представление.

Дело вот в чем:

1. Опять же грани размыты — простейший пример все тот же проект продающих компьютеров-киосков в «Связной». Тут конечно играет роль специфика приложения (а я к тому и веду)! Вы хоть раз видели, чтобы корзина хранилась и обрабатывалась на клиентской стороне ( в браузере ), а запоминалась на серверной стороне, только в конце пути — после нажатия кнопки «Заказать»? У нас именно так.

2. Снова о размытых гранях и все том же оффлайн-веб-приложении(да да, я НЕ опечатался). В приложении достаточно много client-side кода, а Javascript — язык специфичный (коллбэки, замыкания, рантайм-расширения). Кое-где приходится использовать мега-хаки, привести в порядок которые, лично мне не представляется возможным. Как бы вы например справились с ОТЛОЖЕННЫМ ренедерингом в Chrome/Chromium ??? О том, когда и как Chromium рендерит лениво/отложенно планирую отдельную статью.

Так вот — данные вытягиваются при помощи AJAX с сервера — их бы получить в JSON, и рендерить уже на клиенте… Не тут-то было — специфика приложения диктует отдавать не JSON, а HTML — иначе тормоза и снова упираемся в отложенный рендеринг.

3. DOM-Model, CSS и Javascript достаточно тесно связаны между собой. Некоторые вещи хотелось бы сделать при помощи чистой разметки, например плашки с округлыми рамками и тенями — картинкой, однако оказывается, когда их много — начинаются тормоза и приходится использовать CSS3 (нам проще — у нас только Chromium). Для анимации хотелось бы использовать CSS3-Animation, любезно предоставленный движком WebKit, однако практика показывает, что он тормозит требователен к ресурсам.

Итог по DOM+CSS+Javascript+AJAX:

На реализацию сильное влияние оказывают технологии и ширина канала. В итоге часто приходится делать не так, как красиво, а так, как будет хорошо работать. Ну и получается, что говнокод и костыли рулят, как бы сильно меня самого это не удивляло…

Но давайте вернемся к MVC… Хотелось бы сказать, что злоупотреблять им совсем не стоит. Возьмем к примеру простейший говносайт-визитку, которых я в свое время наплодил десятка два.

Как обычно на каждой странице мы имеем сайдбар с новостями. Вот где возникает дилемма и ребро между Controller и View становится мягким…

К делу! Хорошо бы сделать выборку коллекции из пяти последних новостей в контроллере, но лично мне это не нравится по одной простой причине — это придется делать в каждом (почти) контроллере.

Опытный ООПэшник :) скажет, что надо выделить родительский класс CommonController с методом fetchLastNews, который будет запускаться каждый раз непосредственно перед исполнением основного *Action. Не тут-то было!!! Представим, что это не просто пять последних новостей, а некоторая достаточно тяжелая выборка ( и кэширование нам никак не катит ), да еще ко всему используются эти новости лишь на 65% страниц. Наследовать контроллеры от разных базовых классов??? БАРДАК!!! (IMHO)

Но изящный костыль выход есть, если вы имеете шаблонизатор с простым исполнением PHP-кода ( как дела обстоят в других языках я представляю пока слабо) — я обычно использую Macro из limb-project.com или native-php (Smarty хоть и подходит, но исполнение кода в нем мне не нравится {PHP} ЖЕСТЬ {/PHP} )… Так вот, если вы имеете хороший PULL-шаблонизатор — лучше сломать в этом месте грань V|C и сделать выобрку новостей в подшаблоне, который в любой момент может быть подтянут в любой шаблон/Layout инструкцией Include.

Я уже предвижу летящие в меня помидоры от привеженцев MVC ( а я его тоже люблю), но статья призвана не опровергнуть его мощь, а лишь подчеркнуть размытость компонентов, дабы предостеречь разработчиков, видящих в MVC серебрянную пулю.

Серебрянной пули нет — есть золотая середина, но Вам (да и мне) еще предстоит ее найти.
+7
1 сентября 2010, 23:22
6

комментарии (27)

–2
barker #
Основные тезисы статьи опираются на игру терминов MVC/P и на понятие «размытости компонентов». Но на самом деле MVC (как и MVP) не является как таковым шаблоном проектирования (я имею ввиду GoF-паттерны), это набор таких шаблонов, причём самых разнообразных. Т.е. это архитектурный (enterprise) паттерн. Оттого и компоненты размыты, имхо.
0
maxidler #
Ну я и не говорил про банду четырех.

А размытость действительно объясняется архитектурностью (так сказать) паттерна, но далеко не у всех архитектурных паттернов присутствует размытость.

Я намеренно смешал паттерн банды Observer и архитектурный паттерн, т.к. начинающие программисты-архитекторы, у которых еще не сформировался свой взгляд на этот мир узнают MVC еще до прочтения о Singleton. Тут то и начинается поиск серебряных пуль.
0
barker #
А размытость действительно объясняется архитектурностью (так сказать) паттерна, но далеко не у всех архитектурных паттернов присутствует размытость.
Ну а как может не быть размытости? Это же просто архитектура приложения в самом общем виде. У этого «шаблона» нет какой-то конкретной цели и поведения (как например у того же упомянутого Singleton или других шаблонов проектирования), просто общая идея разделения на три компонента.
+5
tenshi #
а что мешает в любой момент в любом контроллере подгрузить другой контроллер?

простой пример:
у тебя был блок выводящий лучшие статьи за день. сам список кэшировался, но информация о пользователях обновлялась потому что они любят менять аватарки, ники и статусы. потом ты решил вывести блок с лучшими комментариями. и опять список берётся из кэша, а за пользователями мы лезем в базу (уже второй раз за запрос). а потом ещё блок, ещё… сколько запросов будет? дофига.

а всё почему? а потому, что в роли контроллера выступает шаблон.

как правильно сделать?

контроллер запрашивает все необходимые блоки, потом вытягивает из них идентификаторы пользователей, одним запросом получает информацию о них, распихивает её по блокам и уже полностью сформированную модель отдаёт в презентатор, который обернёт её в юзер-френдли интерфейс.

почему-то многие программисты стараются переложить координацию действий на верстальщика, вынуждая его для каждой страницы писать отдельный шаблон, хотя он во многих случаях состоит из типовых блоков. да, это позволяет уменьшить число контроллеров, но не на много, так как всё-равно почти на каждый шаблон потребуется свой контроллер.
всё-равно для каждой страницы должна быть где-то расписана логика её формирования. у языка программирования выразительных средств для этого куда больше чем для языка шаблонизации.
0
d3z #
Именно так и следует поступать, а не рвать шаблоны, оправдываясь размытыми границами.
+1
aavezel #
Лично я (т.е. ИМХО) придерживаюсь правила такого: Модель предметной области != ViewModel. взято тут
Т.е. контроллер не должен сам лезть в базу данных/ к другому звену. Он должен запросить модель (которая всё запросит и расформирует), потом из этой модели сформировать ViewModel, а уже её отдать во View.
0
tenshi #
тогда тебе придётся ввести специальные модели для каждой страницы, единственной функцией которых будет координация других моделей — это и будут контроллеры.
0
Bas1l #
Скорее для каждой страницы нужно будет делать маппер, который будет из модели бизнес-логики создавать модель представления, и вызывать этот маппер в каждом экшне контроллера.
0
maxidler #
Наоборт, чтобы не вынуждать писать верстальщика отдельный шаблон я использую инструкцию include/apply шаблонизатора. Потому блок с новостями может быть вставлен в любое место шаблона или лэйаута.
Один раз сверстав составляющую новостей верстальщик пользуется тегом {{include /}}. Но я говорил о том, что данные нужно выбирать только там, где они нужны, а потому иерархия наследования контроллеров (на мой взгляд) здесь не подходит.

И снова возвращаясь к специфике проекта: в каждом проекте это выглядит по разному — все зависит от проекта.
–2
tenshi #
«чтобы не обвешивать комнату картинами, я не рисую красками, а делаю аппликации»

нафига ты сюда приплёл наследование? это единственный способ реюзинга кода, который тебе известен?

конечно по разному. толковых программистов на всех не напасёшься. что уж говорить о толковых архитекторах…
+3
Juraseg #
С тезисом по поводу размытости границ в MVC согласен (да кто не согласен, по-моему это достаточно очевидно). Но вот пример Вы выбрали ужастный. У Вас там размылась граница между МОДЕЛЬЮ и ПРЕДСТАВЛЕНИЕМ, что не есть гут. Выборку послдених пяти новостей (да скольки угодно, хоть пятнадцати, хоть только про Путина), имхо, нужно делать в модели, а контроллер должен уже эту выборку передавать в шаблон (каким угодно способом). Товрищ tenshi выше очень хорошо всё описал.
+1
maxidler #
А выборка и пойдет в модели, но вызов модели будет не в контроллере с последующим PUSHем в шаблон, а непосредственно в шаблоне:
$news = News::findByCriteria(Criteria::contains('name','Путин'),array('limit'=>5,'order'=>array('date'=>'desc'))

0
aavezel #
На мой взгляд автор не осилил само понятие паттерна MVC и что за что в этой связки отвечает :(
Например, в случае с корзиной, которая хранится на клиенте, все нормально. Модель же это не только голые данные. Модель это также код который обрабатывает эту модель. В случае с WebRiсh приложением надо рассматривать само приложение как отдельный слой/звено. Т.е. на клиенте используется JS MVC приложение, которое генерирует HTML как View, классы обработчиков кликов как контроллеры, а модель это тот слой который общается с серверным приложением при помощи ajax(json/html). Серверная часть это тоже отдельное приложение, к которого View — json/html, контроллеры это обработчики post/get запросов, а model это слой общения с другими сервисами/базой данных. Я уж не говорю что другие сервисы/базы данных тоже (возможно) реализуют модель MVC.
Не нужно все приложение загонять в рамки одного MVC, это не правильно…
–1
maxidler #
Автор осилил понятие MVC, похоронив 3 собственных PHP-фреймворка, являясь коммитером одного из мало-популярных фреймворков и найдя порядка 20 багов/граблей в одном из популярнейших фреймворках.

Пример про новости, конечно надуман, но часть реальной ситуации он отражает. Никто не говорит, что в шаблоне будет использован прямой SQL-запрос — там будет что-то вроде $lastNews = News::getLastPublished(5)

На моей практике встречалось немало ситуаций, где приходилось рушить и без того размытые рамки MVC. Где-то ради производительности, где-то ради читабельности кода.

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

Заметьте, я не сказал, что так нужно делать везде — через всю статью проходит мысль о том, что все зависит от специфики проекта. И цель статьи — предупредить шаблонный способ мыслить у новичков, прочитавших предыдущую статью в блоге «Разработка».
+1
BreathLess #
Мне кажется, концепция виджето-сайдбаров вообще порочна, лучше заменить её на подвызовы контроллеров из шаблонов, сократим количество кода, код будет более униформным.
У себя реализовал именно так, доволен.
0
maxidler #
Возможно вы правы, но повторю, что реализацию часто диктует специфика проекта
0
BreathLess #
Либо спецификой являются ошибки проектирования =)
0
maxidler #
Что ж, бывае и такое. Кстати даже не редко!
–1
magnit #
Наследовать контроллеры от разных базовых классов??? БАРДАК!!! (IMHO)

в чем бардак?
+1
megahertz #
Иногда это нормально, иногда действительно предпочтительнее Observer, в некоторых фреймворках есть специальные решения для этой задачи. Бардак начинается, если базовые классы начинают дублировать функционал друг-друга.
0
maxidler #
Если выстраивать иерархию наследования контроллеров — обычно начинается бардак.
Договоримся, что если класс наследует другой класс, то к имени родителя добавляется буква.
Имеем классы a,ab,ac,aba,abb,abc,aca,acb,acc,ad,ada,adb,adba
Если в классе adba нужен функционал, определенный в классе ab, но в классе adb он совсем не нужен, как мы будем поступать? В Pyton мы наверное будем использовать множественное наследование, но я пока плохо представляю, как оно работает. В PHP6 мы будем использовать Traits. А что делать в PHP5 ??? Выход: copy_paste === Бардак.

А если, как сказали Вы наследовать контроллеры от разных БАЗОВЫХ классов — то бардак более чем очевиден.

Или я не так Вас понял?
0
megahertz #
В примере с новостями все просто, обычно решается виджетами, веб-частями, компонентами и т.п. (в зависимости от фреймворка), что прекрасно вписывается в идеалогию MVC. У меня лично с разделением на слои проблем не возникает, правда иногда модель разрастается из-за сложной бизнес-логики в этом случае пишу вспомогательные классы.
0
maxidler #
Опять же! Все зависит от проекта!
Иногда нужно тупо поставить костыль или написать спагетти-код. Это не плохо! Это один из вариантов решения задачи!

Ответственность за то, какой вариант выбрать лежит на разработчике. Городить иерархию классов для вывода символа Евро в одном месте (надуманный пример) — тоже плохой вариант.
0
maxidler #
Когда модель растет — есть тоже несколько вариантов выхода из ситуации. И снова ответственность за выбор лежит на разработчике.

1. Писать Helpers — ваш вариант
2. Строить иерархию наследования.
3. Делить модели
0
sylvio #
В текущем проекте у нас целый зоопарк php+python+orbited+mootools+js+css3 (проект с тяжелым прошлым).
И чтобы не свихнуться во всем этом бардаке нужно не просто думать «о, какой бы сюда паттерн еще впихнуть, чтобы код стал еще запутанее для другого разработчика», а использовать их только по мере необходимости, когда код дублируется.

Золотая середина — это использовать все по мере необходимости.
0
mace #
В ASP.Net MVC для случая с блоком новостей есть специальный механизм RenderAction, который позволяет прорендерить на странице результат экшна из другого контроллера, при чем этот же результат можно загрузить через AJAX-запрос. Я думаю, в РНР такое тоже вполне реализуемо.
0
maxidler #
Реализуемо, но инстанцировать другой контроллер бывает достаточно дорого в плане ресурсов (бутстрапинг контроллера). Снова сказывается специфика проекта.

Через весь топик и мои комментарии проходит мысль: прежде чем применять решение, пусть даже уже вылизанное годами и миллионами разработчиков — подумайте еще раз — подходит ли оно вам?

Бывают случаи когда нужно писать говнокод, ибо привести в порядок унаследованное спагети дорого, а новая фича должна приносить деньги еще вчера и каждый день до релиза стоит компании порядка 1 000 000 рублей.
Будем три дня рефакторить или допишем спагетти за полдня, потом полдня потестируем и завтра — релиз ????????

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.