вторник, 17 февраля 2009 г.

Модель-представление-контроллер. Раз модель, два модель.

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

Я с такой задачей столкнулся при разработке программы, в которой главное окно способно отображать множество различных видов. Окно разделено на две части. Слева - панель, аналогичная Navigation Pane, справа - вид. Один из видов - это графический редактор с нарисованными объектами. Если выбрать этот вид, то слева на панели будет дополнительно выведен полный список объектов. Объекты можно выделять мышкой как в редакторе, так и в списке объектов. Вопрос - где хранить состояние выделенности объектов? Эта информация интересна только для двух представлений: редактора и списка объектов. Ни к модели, ни к вычислительному процессу, ни к другим видам она не имеет отношения.

Попробуем разобраться. Для начала напомню, вкратце, что такое паттер "Model-View-Controller", он же "Модель-Представление-Контроллер", он же MVC.

Паттерн MVC разделяет приложение на три части: обработка данных, вывод и ввод. В модели хранятся данные и реализуется их обработка. Данные в модели представлены в том виде, в котором их удобно обрабатывать (а вовсе не в том, в котором удобно отображать). Модель совершенно не зависит от представления данных в интерфейсе пользователя. Интерфейсом занимаются представления. Они считывают данные из модели, приводят в требуемый вид и отображают пользователю. Если данные нужно показывать разными способами, представлений будет несколько.

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

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

Вот, собственно, и вся архитектура. Осталось заметить, что контроллер нужен далеко не всегда. Разделение "контроллер-представление" нужно вводить только тогда, когда оно действительно требуется. Если их не разделять, то получается частный случай архитектуры MVC, называемый Document-View. Document - это модель, View - это представление + контроллер.

Возвращаемся к вопросу о хранении информации о состоянии выделенности объектов. Варианты:
  • Ввести дополнительное свойство у объекта - флаг "выделен"/"не выделен". Это простейшее решение, но тогда в модели мы будем хранить данные, которые связаны только с представлением - это плохо. А если таких данных нам будет требоваться со временем все больше и больше? И что делать, если у нас подобных представлений "с состоянием" будет несколько и каждому потребуются подобные флаги?
  • Хранить данные прямо в представлении. Хорошая идея, но в каком из двух? Предположим, мы храним информацию о состоянии в представлении A, а представление B знает об этом и берет ее из A. Сразу появляется проблема синхронизации. Если мы изменили состояние выделенности объекта в A, как об этом узнает B?
Мне кажется ответ на поверхности - нужно ввести вторую модель.

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

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

Вот как будет выглядеть схема MVC с двумя моделями.Теперь осталось реализовать идею на практике и посмотреть, насколько удобно будет работать, и какие недостатки вылезут. Поживем - увидим.

P.s. Информацию по паттерну MVC можно найти в книжке
Мартин Фаулер "Архитектура корпоративных программных приложений", 2004 год. У Фаулера книжки интересные и очень полезные, но конкретно эту, лично мне, читать было довольно сложно. Бесконечные перекрестные ссылки на паттерны здорово запутывают. Далее, на сайте у Фаулера есть статья GUI Architectures, написанная примерно в том же стиле. Там рассказывается и про MVC, и про MVP. Но лично мне больше всего понравилось, как тема MVC изложена в книге, которую Фаулер рекомендует: Frank Buschmann "Pattern-oriented software architecture. A system of patterns. Volume 1", 2001. Написано и проще, и подробнее.

1 комментарий:

  1. интересно, как автор относиться к предложенному решению сегодня?
    моё мнение, что тут нужно применять паттерн http://domaindrivendesign.org/node/118 с слоями вида
    1) Presentation
    2) Application
    3) Domain
    4) Infrastructure

    В слое Application должны находиться классы, необходимые для работы приложения, а не моделей предметной области.

    ОтветитьУдалить