Паттерны MVC и Publish-Subscriber

Про шаблон проектирования MVC (Model-View-Controller, Модель-Представление-Контроллер) написано множество статей. Однако, периодически я все равно встречаю непонимание и вопросы: “зачем это надо и для чего все так усложнять?”. Попробую ответить на них. Кроме того, в статье описан паттерн Observer (Publish-Subscribe, наблюдатель, издатель-подписчик), часто применяющийся для организации взаимодействия модели и представления в MVC.

Модель-представление (model-view)

Шаблон MVC направлен на отделение данных и логики (модели) от интерфейса (представления) программы. Так, например, при разработке игры “Тетрис” к модели могут относится массив с фигурами и логика игры, а представление отвечает за отображение этого массива на экране.Такое разделение соответствует принципу единой обязанности (SRP) [1], соблюдение которого обеспечивает:

  • лучшую тестируемость кода, т.к. вы можете писать unit-тесты к модели и виду независимо друг от друга, что позволит более точно выявлять место с ошибкой при провале какого-либо из тестов [2];
  • ослабление зависимостей между видом и представлением, а значит более высокую гибкость. В любой момент заменить одно представление на другое, т.к. теперь модель не зависит от представления. В частности замена представления может быть необходима при переносе программы на мобильную платформу с другим пользовательским интерфейсом;
  • снижение сложности проекта также является следствием ослабления зависимостей. Разрабатывать отдельные части можно дать программистам с более низкой квалификацией.

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

model-view-pattern

Модель и представление игры Тетрис

Это здорово, но откуда представление узнает, что в модели какие-либо данные изменились? – при использовании MVC для этого используется шаблон проектирования publish-subscribe.

Паттерн Publish-Subscribe (Издатель-Подписчик)

Если один или несколько объектов в Вашем проекте должны отслеживать изменения другого объекта – гибким решением будет применение шаблона проектирования Publish-Subscribe.

При изменении своего состояния, издатель оповещает всех своих подписчиков, путем вызова у них какой-либо функции, инициализирующей обновление подписчика. При этом, издатель должен предоставлять функции для добавления и удаления подписчиков. Выделение таких функций в виде интерфейсов (базовых классов подписчика и издателя) позволяет ослабить зависимости в соответствии с принципом инверсии зависимостей (Dependency Inversion Principle) – Издатель будет зависеть лишь от интерфейса подписчика, но не от его конкретного типа [1].

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

publishe-subscribe

Шаблон Publish-Subscribe. Пример использования

На диаграмме показаны основные элементы шаблона:

  • AbstractSubscriber, задает интерфейс подписчиков;
  • EyeChart, один из реальных подписчиков (легко могут быть добавлены другие виды диаграмм). Хранит ссылку на издателя, при помощи которой получает его обновленное состояние после получения сигнала об обновлении данных;
  • AbstractPublisher, задает интерфейс издателей. Может реализовывать этот интерфейс (не являться абстрактным), т.к. все издатели должны одинаково добавлять/удалять подписчика и уведомлять их об обновлении;
  • SpreadSheet, таблица с данными, уведомляющая подписчиков об изменении своих данных.

В результате применения шаблона:

  • ослабляется зависимость издателя от подписчика. Остается зависимость лишь от абстрактного класса, что позволяет без модификации кода издателя добавлять новые типы подписчиков;
  • появляется возможность добавлять и удалять подписчиков во время выполнения программы. Издатель не располагает информацией не только о конкретных типах Подписчиков, но и об их количестве. Так, например, во многих играх наряду с картой, отображающей состояние игрового мира присутствует мини-карта. Обе карты являются подписчиками, а игровой мир – издателем, при этом для отключения мини-карты достаточно удалить соответствующий объект и снять его с подписки.

Model-View-Controller

При разделении системы на модель и представление, остается неопределенным кто должен обрабатывать действия пользователя. Очевидно, измениться должна модель, но ведь пользователь видит и может взаимодействовать лишь с элементами представления… Решением проблемы является создание посредника (шаблон проектирования Mediator), который в контексте MVC называется контроллером и занимается обработкой действий пользователя [3].

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

model-view-controller-sequence-diagram

Диаграмма последовательности шаблона model-view-controller

Контроллер часто содержит достаточно небольшое количество логики, поэтому нередко его обязанности перекладывают на Представление – в этом случае, говорят что используется шаблон проектирования Document-View. Однако, это нарушает принцип единой ответственности (Single Responsibility Principle), а значит приводит к последствиям, перечисленным в начале статьи.


Все сильные стороны шаблона Model-View-Controller можно свести к соблюдению двух принципов SOLID – SRP и DIP, поэтому читателям, незнакомым с этими терминами, я очень советую прочитать соответствующую статью [1].

Целью статьи я ставил объяснение необходимости применения MVC и Publish-Subscribe в определенных случаях, поэтому тут не были описаны история развития этих шаблонов, варианты их реализации и родственные паттерны.

Литература по теме статьи:

  1. SOLID принципы. Рефакторинг: https://pro-prof.com/archives/1914
  2. Юнит-тестирование. Пример. Boost Unit Test: https://pro-prof.com/archives/1549
  3. Шаблон проектирования Mediator: https://pro-prof.com/archives/887
  4. Э. Гамма Приемы объектно-ориентированного проектирования. Паттерны проектирования / Э. Гамма, Р. Хелм, Р. Джонсон, Д. Влиссидес. – СПб.: Питер, 2009. – 366 с.
  5. Тепляков С. В. Паттерны проектирования на платформе .NET – СПб.: Питер, 2015. – 320 с.

Добавить комментарий