Разделение интересов; MVC; Зачем? - PullRequest
31 голосов
/ 12 марта 2009

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

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

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

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

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

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

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

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

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

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

"Разделение интересов держит код для каждой из этих проблем отдельный. Смена интерфейса не требует изменения код бизнес-логики и наоборот. Модель-представление-контроллер (MVC) дизайн шаблон является отличным примером разделяя эти проблемы для лучшего ремонтопригодность программного обеспечения. "

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

Во всяком случае ... наконец-то добрались до сути!

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

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

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

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

Ответы [ 8 ]

32 голосов
/ 12 марта 2009

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

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

"Позже, если мне нужно настроить отображение их имени + небольшой аватар, я могу просто обновить этот метод, и эй-престо, мое приложение обновлено."

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

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

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

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

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

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

25 голосов
/ 12 марта 2009

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

Каркас, который я использую, состоит из трех основных частей:

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

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

Задачей view является отображение данных. Вся разметка 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 самых популярных в боковой панели на подстраницах. Теперь вам нужно либо скопировать приведенный выше код, либо поместить его во включаемый файл с логикой, чтобы проверить, где он отображается.

Что если вы хотите обновить разметку для домашней страницы, чтобы добавить класс "new-post" к сообщениям, которые были созданы сегодня?

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

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

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

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

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

Начнем с модели . У нас будет класс $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;
    } 
}

Теперь для домашней страницы у нас есть контроллер , мы назовем его «home». Давайте представим, что у нас есть схема маршрутизации 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 популярных сообщений. Он получает данные из модели и передает их в представление.

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

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

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

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

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

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

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

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

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

Контроллер подстраниц ... ... 1128 *

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 популярных сообщений. Подстраница view знает, что подстраницы должны помещать список сообщений в 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"}

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

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

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 фреймворкам. Эти платформы помогут вам в работе, но вы, как разработчик, будете отвечать за управление сложностью и обеспечение разделения интересов.

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

3 голосов
/ 12 марта 2009

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

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

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

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

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

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

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

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

2 голосов
/ 13 марта 2009

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

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

2 голосов
/ 12 марта 2009

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

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

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

1 голос
/ 13 марта 2009

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

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

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

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

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

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

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

0 голосов
/ 12 января 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...