Разделение визуализации и бизнес-логики

Модель-представление-контроллер - наиболее известный принцип архитектуры программного обеспечения, в которой модель данных приложения, пользовательский интерфейс и управляющая логика разделены на три отдельных компонента, так, что модификация одного из компонентов оказывает минимальное воздействие на другие компоненты. Описание и некоторые аспекты, в данное время уже исторического характера, описываются в статье Сергей Рогачев, "Обобщенный Model-View-Controller", 2007. Еще ряд примеров в Иван Бодягин, Model-View-Controller в .Net и Андрей Озеров, Триада MVC в действии.

Концепция Model-View-Controller.
Другая более популярная, и возможно более понятная, версия изложения того же самого от Максима Базь, начиная со статьи Знакомимся с терминологией MVC‎, в результате нашего с ним диалога.

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

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

Но иногда под моделью (т.н. пассивная модель) действительно понимают только данные (свойства, методы доступа к ним, получение их с базы данных) без бизнес-логики, тогда при отделении такая модель сама по себе становится бесполезной. Проверим рациональность классической пассивной модели «Модель-представление-контроллер», в большинстве случаев реализации если не создать объекты представления и контроллера, оставшийся объект модель останется полностью „парализованным“, то есть останется не функциональным. Таким образом, мы видим, что декларация независимости в этой модели - фикция.

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

Поэтому начнем с того, что отделение данных от собственно методов (бизнес-логики) является недопустимым в объектно-ориентированном подходе, и как видим если это не выполнить модель будет не функциональна („парализована“). Поэтому далее, чтобы не путаться, нужно уточнить как минимум терминологию и будем говорить о архитектурном принципе под названием «Бизнес-сущность - Визуализация - Контроллер» вместо «Модель-представление-контроллер». Пока мы изменили только слова (затем мы увидим, что это достаточно принципиально) и отказались рассматривать пассивную модель MVC как несостоятельную.

Теперь посмотрим на декларацию для модели «Модель-представление-контроллер»:

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

Но ведь кроме независимости модели от визуального представления, часто нужно и обратное. Нужно также иметь независимость визуального представления от модели. А этого в MVC получается по определению невозможно.

Итак, первое. Слова представление зависит от модели могут означать одно из двух:

  1. визуализация создает (порождает) модель (бизнес-сущность), а с точки зрения классов визуализация агрегирует бизнес-сущность
  2. (или более мягкий вариант) контроллер создает и модель (бизнес-сущность) и визуализацию. И далее есть два варианта, как контроллер распорядится ссылками на эти объекты:
    1. визуализация получает ссылку и на контроллер и на модель, т.е. опять визуализация агрегирует бизнес-сущность
    2. визуализация получает ссылку только на контроллер, но контроллер содержит методы-делегаты, которые прямо адресуются к модели, т.е. формально визуализация не агрегирует бизнес-сущность, а только контроллера, но косвенно (т.к. контроллер в свою очередь агрегирует модель) опять это приводит к агрегировании бизнес-сущности

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

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

Но нам по прежнему нужно выполнить принципиальное условие:

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

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

В C# для визуализации используется современная технология WPF, которая обеспечивает механизм связи (binding), поэтому остается только указать какие именно свойства будут между собой связаны. И встает дилемма - в каком объекте это должно быть сделано? Если это прописать в бизнес-сущности, то мы нарушим правило классы бизнес-сущности не содержат кода визуализации, если это прописать в классе визуализации, то мы нарушим правило классы визуализации содержат только ту логику, которая нужна для работы с интерфейсом пользователя. В модели «модель-представление-контроллер» тем не менее, часто это прописывается в классе визуализации.

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

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

В случае если используется другой язык программирования, и нет возможности воспользоваться binding`ом, можно его заменить приемом рефакторинга, который называется "Дублирование видимых данных" (Duplicate Observed Data) [1].

Но можно и самому реализовать binding, это не очень сложно. Вначале нужно признать, что синхронизация данных между моделью и визуализацией - это низкоуровневая функция, которая должна выполнять отдельным классом MySynh. Как же его написать? У контроллера есть ссылка и на модель и на визуализацию. Он передает эти ссылки MySynh. Кроме этого, чисто в декларативном виде прописывает связи вида ПолеВизуализацииА = СвойствоМоделиB, чисто текстом. На каждое свойство в модели и визуализации написаны методы доступа get(), set() - в которых кроме собственно присваивания нового значения, генерируется событие вида ИзменилосьПолеА. Это нужно не только для синхронизации, а также для вызова логике по смене значения, поэтому это есть в любом случае. Что остается, при инициализации MySynh он подписывается на все события, о синхронизации которых ему сказали заботиться. Когда же возникает реальное событие смены значения, MySynh получает вызов, поискав по своей базе данных о том какие связи надо поддерживать находит нужную, и используя "отражение" (получить ссылку на свойство по его текстовому названию) присваивает новое значение.

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


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

И снова здесь нужно применить контроллер. Но если для данных мы использовали возможности binding'a, то в данном случае нужно использовать технику событий, тогда при инициализации, контроллер проассоциирует сигналы от визуального интерфейса с методами бизнес-сущности, и вместо прямой строгой связи мы получим прозрачную связь.

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

См. далее:: Компоненты (о разделении визуализации и бизнес логики на уровне компонент)

Примечания

править
  1. Рефакторинг, М. Фаулер (2003), стр. 197-204

Литература

править