Предыстория
Статья Неверное использование паттерна проектирования «Мост» / «Bridge» как то так получилось разделила аудиторию на двое. Далее я подумал, сказав А не сказать Б, будет не правильно. Нет я не отказываюсь от своих слов, но я нашел где и как я использовал паттерн «Мост». Т.к. его еще и неверно понимают, кажется альтернативное название «Описатель/тело» — меньше вводит в заблуждение.
Так где же? Оказалось в моем аналоге использования концепции MVC (Модель/Представление/Контроллер).
Поэтому вначале ознакомлю со своей вариацией «Бизнес-сущность — Визуализация — Контроллер». Я уже ее писал, но думаю мало кто с этим знаком. А затем посмотрим где же там «Правильный мост».
P.S. Мне тут выдали кредит доверия, и я обязался написать еще одну статью о усовершенствовании паттерна Flyweight — помню, пишу, надеюсь смогу опубликовать :)
Разделение визуализации и бизнес-логики
Модель-представление-контроллер — наиболее известный принцип архитектуры программного обеспечения, в которой модель данных приложения, пользовательский интерфейс и управляющая логика разделены на три отдельных компонента, так, что модификация одного из компонентов оказывает минимальное воздействие на другие компоненты. Описание и некоторые аспекты, в данное время уже исторического характера, описываются в статье Сергей Рогачев, «Обобщенный Model-View-Controller», 2007. Еще ряд примеров в Иван Бодягин, Model-View-Controller в .Net и Андрей Озеров, Триада MVC в действии.
В реальности использование данной модели сопряженно с рядом проблем и приложения построенные по данной модели, несмотря на декларацию, не являются гибкими и мало связанными. Сама идея отделения визуализации от бизнес-логики в ней декларируется, но связи между моделью, представлением и контроллером построенные совершенно не эффективно.
Отметим, что далеко не каждая «Бизнес-сущность» имеет визуализацию, а как правило, одна и та же «Бизнес-сущность» может, в зависимости от контекста быть использована как с визуализацией, так и без визуализации. При этом она не должна потерять свою функциональность из-за этого. Заметим, что если «Бизнес-сущность» используется без визуализации, то создание объектов представления и контроллера совершенно излишне.
Но иногда под моделью (т.н. пассивная модель) действительно понимают только данные (свойства, методы доступа к ним, получение их с базы данных) без бизнес-логики, тогда при отделении такая модель сама по себе становится бесполезной. Проверим рациональность классической пассивной модели «Модель-представление-контроллер», в большинстве случае реализации если не создать объекты представления и контроллера, оставшийся объект модель останется полностью „парализованным“, то есть останется не функциональным. Таким образом, мы видим, что декларация независимости в этой модели — фикция.
Но позже были развиты представления о активной модели, когда под моделью действительно понимают бизнес-сущность, как совокупность данных и бизнес-логики. Тогда все в порядке, но нужно быть предельно аккуратным, чтобы бизнес-логику не оставить в классах визуализации или контроллере.
Поэтому, начнем с того, что отделение данных от собственно методов (бизнес-логики) является недопустимым в объектно-ориентированном подходе, и как видим если это не выполнить модель будет не функциональна („парализована“). Поэтому далее, чтобы не путаться, нужно уточнить как минимум терминологию и будем говорить о архитектурном принципе под названием «Бизнес-сущность — Визуализация — Контроллер» вместо «Модель-представление-контроллер». Пока мы изменили только слова (затем мы увидим, что это достаточно принципиально) и отказались рассматривать пассивную модель MVC как не состоятельную.
Теперь посмотрим на декларацию для модели «Модель-представление-контроллер»:
В то время как представление и контроллер зависят от модели, модель не зависит ни от представления, ни от контроллера. Это ключевая особенность разделения, которая позволяет работать с моделью, а значит, и с бизнес-логикой приложения, независимо от визуального представления.
Но ведь кроме независимости модели от визуального представления, часто нужно и обратное. Нужно также иметь независимость визуального представления от модели. А этого в MVC получается по определению невозможно.
Итак, первое. Слова представление зависит от модели могут означать одно из двух:
визуализация создает (порождает) модель (бизнес-сущность), а с точки зрения классов визуализация агрегирует бизнес-сущность
(или более мягкий вариант) контроллер создает и модель (бизнес-сущность) и визуализацию. И далее есть два варианта, как контроллер распорядится ссылками на эти объекты:
визуализация получает ссылку и на контроллер и на модель, т.е. опять визуализация агрегирует бизнес-сущность
визуализация получает ссылку только на контроллер, но контроллер содержит методы-делегаты, которые прямо адресуются к модели, т.е. формально визуализация не агрегирует бизнес-сущность, а только контроллера, но косвенно (т.к. контроллер в свою очередь агрегирует модель) опять это приводит к агрегировании бизнес-сущности
Вам не кажется это, как минимум, странным? Получается, что бизнес-сущность, не может решить каким образом себя визуализировать, и визуализировать ли вообще. Более того, пользователь (в данном случае прикладной разработчик) должен создавать объект визуализации или контроллер, и каким то странным образом должен работать через визуализацию или контроллер с бизнес-сущностью. Хотя допустим визуализация может и вообще не потребоваться.
Поэтому первое, что мы признаем — это неверное решение. Визуализация, несмотря на то, что является достаточно специфическим действием, является не больше не меньше как всего лишь одной из функций бизнес-сущности. И поэтому именно бизнес-сущность должна создавать объект визуализации, агрегировать его и решать когда и с помощью какого объекта себя визуализировать, и делать ли это вообще. А вот объект визуализации как раз не должен ничего знать о бизнес-сущности и контроллере, за исключением тех полей данных, которые бизнес-сущность желает визуализировать с помощью интерфейса.
Но нам по прежнему нужно выполнить принципиальное условие:
Классы визуализации содержат только ту логику, которая нужна для работы с интерфейсом пользователя. А классы бизнес-сущности не содержат кода визуализации, зато содержат всю бизнес-логику.
Итак нам нужно, код, обрабатывающий интерфейс пользователя отделить от кода, обрабатывающего бизнес-логику. В то время как поведение (методы) сравнительно легко разделить на части, чего нельзя сделать с данными (полями, свойствами). Данные должны быть в графических элементах и иметь тот же смысл, что и данные в модели предметной области (бизнес-сущности). Поэтому поля содержащие данные должны быть продублированы и должна быть обеспечена их синхронизация (например, значение в графическом элементе TextBox должно быть синхронизировано со значением поля бизнес-сущности). Такая синхронизация требует двухсторонней связи между объектами визуализации и бизнес-сущности. Поэтому в отношении полей данных принципиально невозможно разделить эти два объекта. Но во первых, это можно ограничить только полями данных, а во вторых сделать это прозрачно для объекта визуализации.
В C# для визуализации используется современная технология WPF, которая обеспечивает механизм связи (binding). Поэтому остается только указать какие именно свойства будут между собой связаны. И встает дилемма — в каком объекте это должно быть сделано? Если это прописать в бизнес-сущности, то мы нарушим правило классы бизнес-сущности не содержат кода визуализации, если это прописать в классе визуализации, то мы нарушим правило классы визуализации содержат только ту логику, которая нужна для работы с интерфейсом пользователя. В модели «модель-представление-контроллер» тем не менее, часто это прописывается в классе визуализации.
Но мы поступим по другому. Одна из причин введения третьего, казалось бы совершенно излишнего класса — контроллера, как раз и есть обозначенная выше. Но вначале попробуем уточнить, что такое контроллер сам по себе, и когда он создается. Контроллер в модели «Бизнес-сущность — Визуализация — Контроллер» это по сути протокол взаимодействия между бизнес-сущностью и ее визуализацией. При этом, когда бизнес-сущность определяет, что ей нужно как то визуализироваться, она выбирает и создает объект определенного класса визуализации, после создает тот или иной протокол взаимодействия — контроллер. При этом при создании контроллера она передает ему две ссылки на себя и на выбранную визуализацию. После этого бизнес-сущность теоретически (практически бывает тем не менее сложно этого добиться полностью) не должна больше работать ни с объектом визуализации, ни собственно с контроллером, тем самым обеспечивая полную независимость.
Получается, что контроллер при инициализации прописывает binding, связывая визуальные поля с полями бизнес-сущности, после чего формально эти два объекта визуализации и бизнес-сущности синхронизируется в соответствии с выбранным протоколом, но другого взаимодействия между ними нету. Такой подход и называется прозрачной связью, то есть прямого взаимодействия нету, а происходит только взаимодействие согласно протоколу — коду, который прописан в контроллере.
В случае если используются другой язык программирования, и нет возможности воспользоваться binding`ом, можно его заменить приемом рефакторинга, который называется «Дублирование видимых данных» (Duplicate Observed Data) [1].
Но можно и самому реализовать binding, это не очень сложно. Вначале нужно признать, что синхронизация данных между моделью и визуализацией — это низкоуровневая функция, которая должна выполнять отдельным классом MySynh. Как же его написать? У контроллера есть ссылка и на модель и на визуализацию. Он передает эти ссылки MySynh. Кроме этого, чисто в декларативном виде прописывает связи вида ПолеВизуализацииА = СвойствоМоделиB, чисто текстом. На каждое свойство в модели и визуализации написаны методы доступа get(), set() — в которых кроме собственно присваивания нового значения, генерируется событие вида ИзменилосьПолеА. Это нужно не только для синхронизации, а также для вызова логике по смене значения. Поэтому это есть в любом случае. Что остается, при инициализации MySynh он подписывается на все события, о синхронизации которых ему сказали заботится. Когда же возникает реально событие смены значения, о получает вызов, поискав по своей базе данных о том какие связи надо поддерживать находит нужную. И используя «отражение» (получить ссылку на свойство по его текстовому названию) присваивает новое значение.
В итоге данные в модели и визуализации всегда синхронизируются, всегда одинаковы. И не тот, не другой не имеет ссылок друг на друга. Т.е. они полностью независимы и не знают о существовании друг друга.
Теперь второе. Кроме прозрачной синхронизации данных необходимо обеспечить разделение поведения (методов). Существует по сути разная логика — логика визуализации (например, по нажатию на кнопку могут так или иначе окрасится, стать доступными или видимыми те или иные поля), и бизнес-логика (например, по введенным данным вычисляется определенные значения). Обеспечить логику визуализации нужно в классе визуализации, а бизнес-логику соответственно в бизнес-сущности. Не всегда это бывает тривиально, но этому стоит уделить существенное внимание. Трудность возникает в том, что сигнал от пользователя единственный — например, нажатие кнопки, а вызвать нужно два или более независимых методов. При этом из объекта визуализации мы не можем вызывать методы бизнес-сущности, чтобы не нарушить наши основные принципы разделения.
И снова здесь нужно применить контроллер. Но если для данных мы использовали возможности binding'a, то в данном случае нужно использовать технику событий. Тогда снова при инициализации контроллер, проассоциирует сигналы от визуального интерфейса с методами бизнес-сущности, и снова вместо прямой строгой связи мы получим прозрачную связь.
Таким образом, контроллер нам нужен как обеспечение связи между бизнес-сущностью и визуализацией. И в таком случае, мы действительно имеем практически совершенно независимые классы бизнес-сущностей и визуализации, и выбирая соответствующий контроллер можем в любой комбинации использовать различные бизнес-сущности и визуализации. При этом по сравнению с классической схемой «Модель-представление-контроллер», мы можем не только иметь разную визуализацию для бизнес-сущности, но и определенной визуализацией отображать разные бизнес-сущности. Не говоря уже о проблемах обозначенных выше. При такой организации мы получаем истинное разделение визуализации от бизнес-логики в обе стороны. А также бизнес-сущность по прежнему способна сама принимать решение о визуализации просто применяя тот или иной контроллер для связи с той или иной визуализацией.
Этот текст возможно сложно понять. Тут один хороший человек, написал при моем содействии, возможно, более понятное описание моей идеи. С примерами кода.
Контроллер и есть мост
Если поискать тут «мост», то окажется, что в контроллере есть две ссылки на два «тела». Один на «Бизнес-сущность», второй на «Визуализацию». Почему же он тут оказался, если как я утверждал в первой статье «разделение на абстракцию и реализацию» вредно.
А все потому, что контроллер — это не совсем реализация, и не совсем абстракция. Абстракция и реализация тут находятся вместе как и должно быть в «бизнес-сущности». Но в контроллер вынесена определенная логика связи визуализации с бизнес-логикой. Эта логика связи и есть то, что хорошо вынести в мост. По сути и визуализация — это часть некоторой реализации «бизнес-сущности», но очень особенная. И когда её отделяем очень хорошо развивать «Пользовательский интерфейс» и «Бизнес-логику» отдельно в разных иерархиях. Но через контроллер их связывать. Это и есть правильное использование паттерна «Мост». Но это как бы «Мост с двухсторонним движением», с одной стороны бежим к бизнес-логике (цельной), а по другой к «визуализации»
Автор: tac