Разделение проблем; MVC; Зачем?

В настоящее время я читаю OO, прежде чем приступать к следующему крупному проекту. Чтобы дать вам быстрый фон, я разработчик PHP, работая над веб-приложениями.

Одной из областей, которая меня особенно интересует, является пользовательский интерфейс; в частности, как это сделать и подключить к моей модели OO.

Я читал эту область. Одним из моих фаворитов является создание пользовательских интерфейсов для объектно-ориентированных систем

«Все объекты должны предоставлять свой собственный интерфейс»

Размышляя о моей проблеме, я вижу, что это хорошо работает. Например, я создаю свой «пользовательский» объект для представления того, кто зарегистрировался на моем сайте. Один из моих методов – это «display_yourself» или аналогичный. Я могу использовать это во всем моем коде. Возможно, для начала это будет только их имя. Позже, если мне нужно настроить, чтобы показать свое имя + маленький аватар, я могу просто обновить этот метод, и hey-presto, мое приложение обновлено. Или, если мне нужно сделать свое имя ссылкой на их профиль, эй-престо, я могу легко обновить его с одного места.

Что касается системы ОО; Я думаю, что этот подход работает хорошо. Глядя на другие потоки StackOverflow, я нашел это в разделе «Разделение проблем»: Soc

«В информатике разделение проблем (SoC) – это процесс разбивки компьютерной программы на отдельные функции, которые как можно меньше перекрывают функциональность. Вызывает озабоченность любой интерес или фокус в программе. Обычно проблемы являются синонимами функции или поведение. Прогресс в направлении SoC традиционно достигается посредством модульности и инкапсуляции с помощью скрытия информации ».

На мой взгляд, я достиг этого. Мой пользовательский объект скрывает всю информацию. У меня нет никаких мест в моем коде, где я говорю $ user-> get_user_name (), прежде чем отображать его.

Однако это, похоже, противоречит тому, что, по-видимому, думают другие люди как «лучшая практика».

Чтобы процитировать «выбранный» (зеленый) ответ на тот же вопрос:

«Разделение проблем сводится к тому, что код для каждой из этих проблем разделен. Изменение интерфейса не должно требовать изменения кода бизнес-логики и наоборот. Шаблон проектирования Model-View-Controller (MVC) является прекрасным примером разделения этих проблем для лучшей поддержки программного обеспечения ».

Почему это помогает улучшить совместимость с программным обеспечением? Разумеется, с MVC мой взгляд должен знать о модели очень много? Прочтите статью JavaWorld для подробного обсуждения по этому вопросу: Создание пользовательских интерфейсов для объектно-ориентированных систем

В любом случае … наконец-то дойду до сути!

1. Кто-нибудь может рекомендовать любые книги, которые подробно обсуждают это? Мне не нужна книга MVC; Я не продаюсь на MVC. Я хочу книгу, в которой обсуждаются OO / UI, потенциальные проблемы, возможные решения и т. Д. (Возможно, включая MVC). Объектно-ориентированная эвристика дизайна Артура Риеля

(и это отличная книга!), но я хочу кое-что, что более подробно.

2. Может ли кто-нибудь выдвинуть аргумент, который также объясняется, как статья JavaWorld Аллена Голуба, которая объясняет, почему MVC – хорошая идея?

Большое спасибо за каждого, кто может помочь мне сделать вывод об этом.

Это неудача в том, как часто учат ООП, используя такие примеры, как rectangle.draw () и dinosaur.show (), которые не имеют абсолютно никакого смысла.

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

«Позже, если мне нужно настроить, чтобы показать свое имя + маленький аватар, я могу просто обновить этот метод, и hey-presto, мое приложение обновлено».

Подумайте о том, что это маленький кусочек на мгновение. Теперь взгляните на Stack Overflow и обратите внимание на все места, где отображается ваше имя пользователя. В каждом случае это выглядит одинаково? Нет, наверху вы только что получили конверт рядом с вашим именем пользователя, за которым следуют ваша репутация и значки. В теме вопроса у вас есть ваш аватар, за которым следует ваше имя пользователя с вашей репутацией и значками ниже. Как вы думаете, есть ли пользовательский объект с такими методами, как getUserNameWithAvatarInFrontOfItAndReputationAndBadgesUnderneath ()? Неа.

Объект связан с данными, которые он представляет, и методами, которые воздействуют на эти данные. У вашего пользовательского объекта, вероятно, будут элементы firstName и lastName, а также необходимые геттеры для извлечения этих фрагментов. Он также может иметь удобный метод, например toString () (в терминах Java), который возвращает имя пользователя в общем формате, например, имя, за которым следует пробел, а затем фамилия. Кроме того, пользовательский объект не должен делать ничего другого. Клиент должен решить, что он хочет делать с объектом.

Возьмите пример, который вы предоставили нам с пользовательским объектом, а затем подумайте о том, как вы будете делать следующее, если вы создали в нем «UI»:

  1. Создайте экспорт CSV, показывающий всех пользователей, заказанных по имени. Например, Lastname, Firstname.
  2. Обеспечьте как тяжелый GUI, так и веб-интерфейс для работы с пользовательским объектом.
  3. Покажите аватар рядом с именем пользователя в одном месте, но покажите только имя пользователя в другом.
  4. Предоставьте список пользователей RSS.
  5. Покажите имя пользователя жирным шрифтом в одном месте, выделено курсивом в другом и как гиперссылка в другом месте.
  6. Показывает средний начальный уровень пользователя.

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

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

Ниже приведен подход, который я использую при создании веб-сайтов на PHP с использованием шаблона MVC / разделения проблем:

Я использую три основных элемента:

  • Модели – классы PHP. Я добавляю им методы для извлечения и сохранения данных. Каждая модель представляет собой отдельный тип сущности в системе: пользователи, страницы, сообщения в блогах
  • Просмотров – Smarty шаблоны. Здесь находится html.
  • Контроллеры – классы PHP. Это мозги приложения. Обычно URL-адреса на сайте вызывают методы класса. example.com/user/show/1 вызовет метод $ user_controller-> show (1). Контроллер извлекает данные из модели и передает ее в представление.

Каждая из этих частей имеет определенную работу или «заботу». Задача модели – обеспечить чистый интерфейс для данных. Как правило, данные сайта хранятся в базе данных SQL. Я добавляю методы модели для извлечения данных и сохранения данных.

Задача представления – отображать данные. Вся разметка HTML отображается в представлении. Логика для обработки зебра-полосания для таблицы данных идет в представлении. Код для обработки формата, в котором должна отображаться дата, отображается в представлении. Мне нравится использовать шаблоны Smarty для просмотров, потому что он предлагает некоторые полезные функции для обработки таких вещей.

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

Давайте посмотрим на пример того, как они объединяются и где преимущества лежат:

Представьте себе простой блог-сайт. Основная часть данных – это сообщение. Также представьте, что сайт отслеживает количество просмотров сообщения. Для этого мы создадим таблицу SQL:

posts id date_created title body hits 

Теперь предположим, что вы хотели бы показать 5 самых популярных сообщений. Вот что вы можете увидеть в приложении, отличном от MVC:

 $sql = "SELECT * FROM posts ORDER BY hits DESC LIMIT 5"; $result = mysql_query($sql); while ($row = mysql_fetch_assoc($result)) { echo "<a href="post.php?id=$row['id']">$row['title']</a><br />"; } 

Этот фрагмент довольно прост и работает хорошо, если:

  1. Это единственное место, где вы хотите показать самые популярные должности
  2. Вы никогда не хотите изменять, как это выглядит
  3. Вы никогда не решаете изменить то, что «популярный пост»

Представьте, что вы хотите показать 10 самых популярных сообщений на домашней странице и 5 самых популярных в боковой панели на подстраницах. Теперь вам нужно либо скопировать код выше, либо поместить его в файл include с логикой, чтобы проверить, где он отображается.

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

Предположим, вы решили, что почта популярна, потому что у нее много комментариев, а не хитов. База данных изменится, чтобы отразить это. Теперь каждое место в вашем приложении, которое показывает популярные сообщения, должно быть обновлено, чтобы отразить новую логику.

Вы начинаете видеть снежный ком сложной формы. Легко понять, как все становится все труднее поддерживать в ходе проекта. Кроме того, рассмотрите сложность, когда несколько разработчиков работают над проектом. Должен ли дизайнер консультироваться с разработчиком базы данных при добавлении класса в вывод?

Принятие подхода MVC и обеспечение разделения проблем в вашем приложении могут смягчить эти проблемы. В идеале мы хотим разделить его на три области:

  1. логика данных
  2. логика приложения
  3. и логика отображения

Давайте посмотрим, как это сделать:

Мы начнем с модели . У нас $post_model класс $post_model и дадим ему метод get_popular() . Этот метод вернет массив сообщений. Кроме того, мы укажем ему параметр, чтобы указать количество возвращаемых сообщений:

 post_model.php class post_model { public function get_popular($number) { $sql = "SELECT * FROM posts ORDER BY hits DESC LIMIT $number"; $result = mysql_query($sql); while($row = mysql_fetch_assoc($result)) { $array[] = $row; } return $array; } } 

Теперь для главной страницы у нас есть контроллер , мы будем называть его «домашним». Представим себе, что у нас есть схема маршрутизации URL-адресов, которая вызывает наш контроллер при запросе домашней страницы. Это задача получить популярные сообщения и дать их правильному виду:

 home_controller.php class home_controller { $post_model = new post_model(); $popular_posts = $post_model->get_popular(10); // This is the smarty syntax for assigning data and displaying // a template. The important concept is that we are handing over the // array of popular posts to a template file which will use them // to generate an html page $smarty->assign('posts', $popular_posts); $smarty->view('homepage.tpl'); } 

Теперь посмотрим, как будет выглядеть представление:

 homepage.tpl {include file="header.tpl"} // This loops through the posts we assigned in the controller {foreach from='posts' item='post'} <a href="post.php?id={$post.id}">{$post.title}</a> {/foreach} {include file="footer.tpl"} 

Теперь у нас есть основные части нашего приложения и можно увидеть разделение проблем.

Модель связана с получением данных. Он знает о базе данных, знает о запросах SQL и операторах LIMIT. Он знает, что он должен передать хороший массив.

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

Представление знает, что массив сообщений должен отображаться как серия тегов-афоров с тегами разрыва после них. Он знает, что сообщение имеет заголовок и идентификатор. Он знает, что заголовок сообщения должен использоваться для текста привязки и что идентификатор posts должен использоваться в href. Представление также знает, что на странице должен быть верхний и нижний колонтитулы.

Также важно упомянуть, что каждая деталь не знает.

Модель не знает, что популярные сообщения отображаются на главной странице.

Контроллер и представление не знают, что сообщения хранятся в базе данных SQL.

Контроллер и модель не знают, что каждая ссылка на сообщение на главной странице должна иметь после нее тег break.

Таким образом, в этом состоянии мы установили четкое разделение проблем между логикой данных (моделью), логикой приложения (контроллером) и логикой отображения (вид). Ну что теперь? Мы взяли короткий простой PHP-фрагмент и разбили его на три путаных файла. Что это дает нам?

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

  1. Показывать популярные сообщения в боковой панели на подстраницах
  2. Выделить новые сообщения с дополнительным классом css
  3. Измените базовое определение «популярного сообщения»,

Чтобы показать популярные сообщения на боковой панели, мы добавим два файла на нашу страницу:

Контроллер подстраницы …

 subpage_controller.php class subpage_controller { $post_model = new post_model(); $popular_posts = $post_model->get_popular(5); $smarty->assign('posts', $popular_posts); $smarty->view('subpage.tpl'); } 

… и шаблон подстраницы:

 subpage.tpl {include file="header.tpl"} <div id="sidebar"> {foreach from='posts' item='post'} <a href="post.php?id={$post.id}">{$post.title}</a> {/foreach} </div> {include file="footer.tpl"} 

Новый контроллер подстраницы знает, что на подстранице должно отображаться только 5 популярных сообщений. В представлении подстраницы известно, что подстраницы должны помещать список сообщений внутри div боковой панели.

Теперь на главной странице мы хотим выделить новые сообщения. Мы можем добиться этого, изменив страницу homepage.tpl.

 {include file="header.tpl"} {foreach from='posts' item='post'} {if $post.date_created == $smarty.now} <a class="new-post" href="post.php?id={$post.id}">{$post.title}</a> {else} <a href="post.php?id={$post.id}">{$post.title}</a> {/if} {/foreach} {include file="footer.tpl"} 

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

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

 post_model.php class post_model { public function get_popular($number) { $sql = "SELECT * , COUNT(comments.id) as comment_count FROM posts INNER JOIN comments ON comments.post_id = posts.id ORDER BY comment_count DESC LIMIT $number"; $result = mysql_query($sql); while($row = mysql_fetch_assoc($result)) { $array[] = $row; } return $array; } } 

Мы увеличили сложность логики «популярного поста». Однако, как только мы внесли это изменение в модель , в одном месте новая логика применяется повсюду. Домашняя страница и подстраница, без каких-либо изменений, теперь будут отображать популярные сообщения, основанные на комментариях. Наш дизайнер не должен был участвовать в этом. На разметку не влияет.

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

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

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

Кроме того, при запуске проекта могут возникнуть некоторые накладные расходы, чтобы все эти части работали вместе. Существует множество инфраструктур, которые предоставляют строительные блоки для моделей, контроллеров, шаблонов двигателей, маршрутизации URL и т. Д. См. Много других сообщений о SO для предложений по фреймворкам PHP MVC. Эти рамки помогут вам начать работу, но вы, как разработчик, отвечаете за управление сложностью и обеспечиваете разделение проблем.

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

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

Во-первых, в MVC модель и представление имеют некоторое взаимодействие, но представление действительно связано с контрактом, а не с реализацией. Вы можете перейти к другим моделям, которые придерживаются одного и того же контракта и все еще смогут использовать представление. И, если вы думаете об этом, это имеет смысл. Пользователь имеет имя и фамилию. Вероятно, у него также есть имя входа и пароль, хотя вы можете или не можете связать это с «контрактом» пользователя. Дело в том, что, как только вы определите, что такое пользователь, это вряд ли сильно изменится. Вы могли бы что-то добавить к этому, но вряд ли вы часто это уберете.

В представлении у вас есть указатели на модель, которая придерживается этого контракта, но я могу использовать простой объект:

  public class User { public string FirstName; public string LastName; } 

Да, я понимаю, что общественные поля плохие. 🙂 Я также могу использовать DataTable в качестве модели, пока он выдает FirstName и LastName. Это может быть не лучший пример, но дело в том, что модель не привязана к виду. Представление привязано к контракту, и конкретная модель придерживается этого контракта.

Я не слышал, чтобы каждый объект имел свой собственный интерфейс. Существуют, по существу, два типа объектов: состояние и поведение. Я видел примеры, которые имеют как состояние, так и поведение, но они, как правило, находятся в системах, которые не очень проверяемы, что мне не нравится. В конечном счете, каждый объект состояния должен быть подвергнут воздействию какого-либо пользовательского интерфейса, чтобы не заставлять ИТ-специалистов обрабатывать все обновления непосредственно в хранилище данных, возможно, но иметь собственный пользовательский интерфейс? Я должен был бы увидеть, что это написано в объяснении, чтобы попытаться понять, что делает пользователь.

Что касается SoC, reasaon для четкого отображения вещей – это возможность переключения слоев / уровней без перезаписи всей системы. В общем, приложение действительно расположено в бизнес-уровне, так что часть не может быть легко отключена. Данные и пользовательский интерфейс должны быть довольно легко переключаться в хорошо спроектированной системе.

Что касается книг по пониманию ООП, я, как правило, люблю книги по шаблонам, поскольку они представляют собой более практичные способы понимания концепций. Вы можете найти материал для праймера в Интернете. Если вы хотите, чтобы книга по языковым языкам не была описана, и немного подумайте, книга «Банда четырех» – это хорошее место для начала. Для более творческих типов, я бы сказал, Heads Up Design Patterns.

Проблема с идеей, что все ваши объекты знают, как отображать себя, состоит в том, что каждый объект может отображаться только одним способом. Что произойдет, если вы хотите предоставить подробный вид пользователя и сводный вид. Что произойдет, если вы хотите отобразить представление, объединяющее несколько объектов (например, пользователей и связанных с ними адресов). Если вы отделяете свои бизнес-объекты (пользователей) от вещей, которые знают, как их отображать, тогда у вас больше нет кода для написания, вы просто разделяете его на разные места.

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

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

Может ли кто-нибудь указать аргумент […], который объясняет, почему MVC – хорошая идея?

Это помогает вам осознать, что делает ваш код, потому что они изолированы друг от друга.

  • Рассмотрите объем кода, который будет входить в этот единственный класс, если вы хотите разоблачить ту же информацию не только как Html в пользовательском интерфейсе, но как часть RSS, JSON, службу отдыха с XML, [вставить что-то еще] ,
  • Это непрозрачная абстракция, а это означает, что она пытается дать вам понять, что это будет единственная часть, которая когда-либо узнает эти данные, но это не может быть полностью правдой. Допустим, вы хотите предоставить услугу, которая будет интегрирована с несколькими внешними третьими лицами. Вам будет очень сложно заставить их использовать ваш конкретный язык для интеграции с вашим сервисом (так как это класс, единственный элемент, который может когда-либо использовать данные), или, если с другой стороны, вы раскрываете некоторые свои данные вы не скрываете данные от сторонних систем.

Обновление 1: я подробно рассмотрел всю статью и, будучи старой статьей (99), на самом деле не имеет отношения к MVC, как мы ее знаем сегодня, к объектно-ориентированным и не имеет аргументов, которые относятся к SRP.

Вы могли бы прекрасно соответствовать тому, что он сказал, и обрабатывать вышеупомянутый сценарий, который я упомянул, с конкретными классами, ответственными за перевод публичного контракта объекта в разные форматы: основная проблема заключалась в том, что у нас не было четкого места для обработки изменений а также что мы не хотим, чтобы информация повторялась на всем протяжении. Таким образом, в случае с html вы можете отлично иметь элемент управления, который отображает информацию, или класс, который преобразует его в html или [вставляют здесь повторное использование mecanism].

Кстати, у меня была вспышка с бит RMI. Во всяком случае, в этом примере вы можете видеть, что он привязан к коммуникационному меканизму. Тем не менее, каждый вызов метода удаленно обрабатывается. Я думаю, что он также очень беспокоился о том, что разработчики, имеющие код, вместо того, чтобы получать один объект и работать с возвращенной информацией, имели множество небольших звонков, чтобы получить тонны разных фрагментов информации.

Ps. Я предлагаю вам прочитать информацию о DDD и Solid, которая, как я уже сказал, для SRP, я бы не сказал, что это тот тип вещей, который автор делал с жалобами

Я не знаю хороших книг по теме MVC, но по собственному опыту. Например, в веб-разработке вы работаете с дизайнерами, а иногда и с dbas. Разделение логики из презентации позволяет вам лучше работать с людьми с разными навыками, потому что дизайнеру не нужно много говорить о кодировании и наоборот. Кроме того, для концепции DRY вы можете сделать свой код менее повторяющимся и более простым в обслуживании. Ваш код будет более многоразовым и сделает вашу работу намного проще. Это также сделает вас лучшим разработчиком, потому что вы станете более организованным и подумаете о программировании по-другому. Поэтому, даже если вам нужно работать над чем-то, что не является MVC, у вас может быть другой подход к архитектуре проекта, потому что вы понимаете концепции MVC.

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

Мое 2c .. другое, что вы могли бы сделать, кроме того, что было сказано, это использовать Decorators ваших объектов User. Таким образом, вы можете украсить пользователя по-разному в зависимости от контекста. Таким образом, вы получите WebUser.class, CVSUser.class, RSSUser.class и т. Д.

Я действительно не делаю этого таким образом, и это может стать беспорядочным, но это помогает избежать того, чтобы клиентский код не мог извлечь много информации из вашего Пользователя. Это может быть интересно посмотреть 😉

  • Почему методы getter и setter являются злыми (JavaWorld)
  • Декоратор