Model-View-Controller

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск
Шаблон проектирования
Model-View-Controller
MVC-Process.png
Структура:
  • Модель
  • Контроллер
  • Представление

Model-view-controller (MVC, «модель-представление-контроллер», «модель-вид-контроллер») — схема использования нескольких шаблонов проектирования, с помощью которых модель приложения, пользовательский интерфейс и взаимодействие с пользователем разделены на три отдельных компонента таким образом, чтобы модификация одного из компонентов оказывала минимальное воздействие на остальные. Данная схема проектирования часто используется для построения архитектурного каркаса, когда переходят от теории к реализации в конкретной предметной области[1].

История[править | править вики-текст]

Концепция MVC была описана Трюгве Реенскаугом в 1979 году[2] , работавшим в то время над языком программирования «Smalltalk» в научно-исследовательском центре «Xerox PARC». Оригинальная реализация описана в статье «Applications Programming in Smalltalk-80: How to use Model-View-Controller»[3]. Затем Джим Алтофф с командой разработчиков реализовали версию MVC для библиотеки классов Smalltalk-80.

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

  1. Пассивная модель — модель не имеет никаких способов воздействовать на представление или контроллер, и используется ими в качестве источника данных для отображения. Все изменения модели отслеживаются контроллером и он же отвечает за перерисовку представления, если это необходимо. Такая модель чаще используется в структурном программировании, так как в этом случае модель представляет просто структуру данных, без методов их обрабатывающих;
  2. Активная модель — модель оповещает о произошедших в ней изменениях те представления, которые подписались на получение таких оповещений. Это позволяет сохранить независимость модели от контроллеров и представлений.

Классической реализацией концепции MVC принято считать версию именно с активной моделью.

С развитием объектно-ориентированного программирования и понятия о шаблонах проектирования — был создан ряд модификаций концепции MVC, которые при реализации у разных авторов могут отличаться от оригинальной. Так, например, Эриан Верми в 2004 году описал пример обобщённого MVC[4].

В предисловии к диссертации «Naked objects» Ричарда Поусона (Richard Pawson), — Трюгве Реенскауг упоминает свою неопубликованную наиболее раннюю версию MVC, согласно которой:

  • Модель относилась к «разуму» пользователя;
  • Под представлением имелся в виду редактор, позволяющий пользователю просматривать и обновлять информацию;
  • Контроллер являлся инструментом для связывания представлений воедино и применялся пользователем для решения его задач[5].

Назначение[править | править вики-текст]

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

  1. К одной модели можно присоединить несколько видов, при этом не затрагивая реализацию модели. Например, некоторые данные могут быть одновременно представлены в виде электронной таблицы, гистограммы и круговой диаграммы;
  2. Не затрагивая реализацию видов, можно изменить реакции на действия пользователя (нажатие мышью на кнопке, ввод данных) — для этого достаточно использовать другой контроллер;
  3. Ряд разработчиков специализируется только в одной из областей: либо разрабатывают графический интерфейс, либо разрабатывают бизнес-логику. Поэтому возможно добиться того, что программисты, занимающиеся разработкой бизнес-логики (модели), вообще не будут осведомлены о том, какое представление будет использоваться.

Концепция[править | править вики-текст]

Концепция MVC позволяет разделить данные (модель), представление и обработку действий (производимую контроллером) пользователя на три отдельных компонента:

  • Модель (англ. Model):
    • Предоставляет знания: данные и методы работы с этими данными;
    • Реагирует на запросы, изменяя своё состояние;
    • Не содержит информации, как эти знания можно визуализировать;
  • Представление, вид (англ. View) — отвечает за отображение информации (визуализацию). Часто в качестве представления выступает форма (окно) с графическими элементами;
  • Контроллер (англ. Controller) — обеспечивает связь между пользователем и системой: контролирует ввод данных пользователем и использует модель и представление для реализации необходимой реакции.

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

Для реализации схемы «Model-View-Controller» используется достаточно большое число шаблонов проектирования (в зависимости от сложности архитектурного решения), основные из которых — «наблюдатель», «стратегия», «компоновщик»[6]:

  • Наиболее типичная реализация — в которой вид отделён от модели путём установления между ними протокола взаимодействия, использующего «аппарат событий» (обозначение «событиями» определённых ситуаций, возникающих в ходе выполнения программы, — и рассылка уведомлений о них всем тем, кто подписался на получение): при каждом особом изменении внутренних данных в модели (обозначенном как «событие»), она оповещает о нём те зависящие от неё представления, которые подписаны на получение такого оповещения — и представление обновляется. Так используется шаблон «наблюдатель»;
  • При обработке реакции пользователя — представление выбирает, в зависимости от нужной реакции, нужный контроллер, который обеспечит ту или иную связь с моделью. Для этого используется шаблон «стратегия», или вместо этого может быть модификация с использованием шаблона «команда»;
  • Для возможности однотипного обращения с подобъектами сложно-составного иерархического вида — может использоваться шаблон «компоновщик». Кроме того, могут использоваться и другие шаблоны проектирования — например, «фабричный метод», который позволит задать по умолчанию тип контроллера для соответствующего вида.

Наиболее частые ошибки[править | править вики-текст]

Начинающие программисты (особенно в веб-программировании, где аббревиатура «MVC» стала популярна) очень часто трактуют архитектурную модель MVC как пассивную модель MVC: модель выступает исключительно совокупностью функций для доступа к данным, а контроллер содержит бизнес-логику. В результате — код моделей по факту является средством получения данных из СУБД, а контроллер — типичным модулем, наполненным бизнес-логикой (см. «скрипт» в терминологии веб-программирования). В результате такого понимания — MVC-разработчики стали писать код, который Pádraic Brady (известный в кругах сообщества «Zend Framework») охарактеризовал как «ТТУК» («Толстые, тупые, уродливые контроллеры»; Fat Stupid Ugly Controllers)[7]:

Среднестатистический ТТУК получал данные из БД (используя уровень абстракции базы данных, делая вид, что это модель) или манипулировал, проверял, записывал, а также передавал данные в Представление. Такой подход стал очень популярен потому, что использование таких контроллеров похоже на классическую практику использования отдельного php-файла для каждой страницы приложения.

Но в объектно-ориентированном программировании используется активная модель MVC, где модель — это не только совокупность кода доступа к данным и СУБД, но и вся бизнес-логика; также, модели могут инкапсулировать в себе другие модели. Контроллеры же, — как элементы информационной системы, — ответственны лишь за:

  • Приём запроса от пользователя;
  • Анализ запроса;
  • Выбор следующего действия системы, соответственно результатам анализа (например, передача запроса другим элементам системы);

Только в этом случае контроллер становится «тонким» и выполняет исключительно функцию связующего звена (glue layer) между отдельными компонентами информационной системы.

Пример[править | править вики-текст]

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

<?php

namespace app\models;

use Yii;

/**
 * This is the model class for table "posts".
 *
 * @property integer $id
 * @property string $alias
 * @property string $title
 * @property string $body
 * @property string $created
 * @property string $modified
 */
class Page extends \yii\db\ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'posts';
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['alias'], 'required'],
            [['body'], 'string'],
            [['created', 'modified'], 'safe'],
            [['alias'], 'string', 'max' => 250],
            [['title'], 'string', 'max' => 255]
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => Yii::t('app', 'ID'),
            'alias' => Yii::t('app', 'Alias'),
            'title' => Yii::t('app', 'Title'),
            'body' => Yii::t('app', 'Body'),
            'created' => Yii::t('app', 'Created'),
            'modified' => Yii::t('app', 'Modified'),
        ];
    }
}

Тогда контроллер может быть таким:

<?php

namespace app\controllers;

use Yii;
use app\models\Page;
use app\models\PageSearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
use yii\filters\AccessControl;

/**
 * PageController implements the CRUD actions for Page model.
 */
class PageController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'only' => ['index', 'update', 'delete', 'view'],
                'rules' => [
                    [
                        'actions' => ['index', 'update', 'delete', 'view'],
                        'allow' => true,
                        'roles' => ['@'],
                    ],
                ],
            ],
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'delete' => ['post'],
                ],
            ],
        ];
    }

    /**
     * Lists all Page models.
     * @return mixed
     */
    public function actionIndex()
    {
        $searchModel = new PageSearch;
        $dataProvider = $searchModel->search(Yii::$app->request->getQueryParams());

        return $this->render('index', [
            'dataProvider' => $dataProvider,
            'searchModel' => $searchModel,
        ]);
    }

    /**
     * Displays a single Page model.
     * @param integer $id
     * @return mixed
     */
    public function actionView($id)
    {
        return $this->render('view', [
            'model' => $this->findModel($id),
        ]);
    }

    /**
     * Creates a new Page model.
     * If creation is successful, the browser will be redirected to the 'view' page.
     * @return mixed
     */
    public function actionCreate()
    {
        $model = new Page;

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        } else {
            return $this->render('create', [
                'model' => $model,
            ]);
        }
    }

    /**
     * Updates an existing Page model.
     * If update is successful, the browser will be redirected to the 'view' page.
     * @param integer $id
     * @return mixed
     */
    public function actionUpdate($id)
    {
        $model = $this->findModel($id);

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        } else {
            return $this->render('update', [
                'model' => $model,
            ]);
        }
    }

    /**
     * Deletes an existing Page model.
     * If deletion is successful, the browser will be redirected to the 'index' page.
     * @param integer $id
     * @return mixed
     */
    public function actionDelete($id)
    {
        $this->findModel($id)->delete();

        return $this->redirect(['prod', 'alias' => '123']);
    }

    /**
     * Finds the Page model based on its primary key value.
     * If the model is not found, a 404 HTTP exception will be thrown.
     * @param integer $id
     * @return Page the loaded model
     * @throws NotFoundHttpException if the model cannot be found
     */
    protected function findModel($id)
    {
        if (($model = Page::findOne($id)) !== null) {
            return $model;
        } else {
            throw new NotFoundHttpException('The requested page does not exist.');
        }
    }
}

Представление:

<div class="page-view">

<h1><?= Html::encode($this->title) ?></h1>	
<?= $model->body ?>

</div>

См. также[править | править вики-текст]

Примечания[править | править вики-текст]

Литература[править | править вики-текст]

  • Адам Фримен. ASP.NET MVC 4 с примерами на C# 5.0 для профессионалов, 4-е издание = Pro ASP.NET MVC 4, 4th edition. — М.: «Вильямс», 2013. — 688 с. — ISBN 978-5-8459-1867-3.
  • Джесс Чедвик и др. ASP.NET MVC 4: разработка реальных веб-приложений с помощью ASP.NET MVC = Programming ASP.NET MVC 4: Developing Real-World Web Applications with ASP.NET MVC. — М.: «Вильямс», 2013. — 432 с. — ISBN 978-5-8459-1841-3.

Ссылки[править | править вики-текст]