Охота на мифический MVC. Пользовательский интерфейс: как делить и куда помещать логику
Детектив по материалам IT. Часть вторая
Чтобы не было разочарований,»считаю долгом предупредить, что кот древнее и неприкосновенное животное» что в этой части будет продолжено исследование «исторического хлама» и написанное здесь практически бесполезно при работе с конкретными фреймворками.
Ну, а тем, кто не испугался, я попробую показать, как же изначально выглядело деление Вид/Контроллер и какие интересные решения можно найти в этой области на сегодняшний день. Ссылки на первоисточники приведены в начале первой части.
Итак, переходим к Пользовательскому Интерфейсу. Не смотря на то что Вид определяется как модуль, отображающий Модель — »а view is a (visual) representation of its model», на практике к Виду, как правило, просто относят все графические элементы GUI, то есть Видом считается все то, что мы видим на экране ЭВМ.
Понятно, что тут содержится некое противоречие, поскольку такие графические компоненты как меню, кнопки, тулбары служат не для отображения информации о системе, а прежде всего для управления системой. Клавиатура и мышь всегда были средством управления программой и находились в «ведомости» Контроллера (как бы его не трактовали). Поэтому кажется нелогичным и странным, что кнопки, сделанные из пластмассы, считаются элементами управления и относятся к Контроллеру, а кнопки, нарисованные на экране, и по сути выполняющие те же самые функции (производить входящие события), почему то относят к Виду.
Кроме того, поскольку разумно считается что в графических элементах не хорошо «прописывать логику», то отсюда почему-то делается вывод, что Вид должен быть тонким и тупым (dumb View) и соответственно логику работы интерфейса можно обнаружить в самых неожиданных местах, вплоть до доменной модели (даже Фаулер пишет о «загрязнении» Модели настройками интерфейса GUI Architectures).
Интерфейс. Деление на Вид и Контроллер
И вновь для того, чтобы прояснить ситуацию предлагаю обратиться к «архитектурным принципам» и первоисточникам. Когда речь идет о декомпозиции то одно из основных «архитектурных» правил заключается в том, что делить на модули нужно прежде всего исходя из тех задач, которые решает система. Каждый модуль должен отвечать за решение какой-то определенной задачи (желательно одной) и выполнять соответствующую ей функцию.
Соответственно, для того, чтобы понять как следует делить пользовательский интерфейс на модули, в первую очередь надо проанализировать что он делает и какие задачи решает. Ведь интерфейс это функция, а не графические элементы. И функция эта заключается в том, чтобы обеспечивать взаимо-действие пользователя с системой. Что означает:
- выводить и удобно отображать пользователю информацию о системе
- вводить данные и команды пользователя в систему (передавать их системе)
То есть, в общем случае пользовательский интерфейс является двунаправленным и решает две задачи, одна из которых связана с выводом и представлением информации, а вторая — с вводом команд и данных. Вот эти-то задачи и определяют то, как нужно делить интерфейс на модули. Вновь смотрим картинку из первоисточника (Реенскауг):
Как видно пользовательский интерфейс довольно естественно и логично делится на два относительно независимых функциональных модуля. Причем такое функциональное деление, основанное на решаемых задачах, универсально. Не важно звуковой это интерфейс, графический или сенсорный в нем должен быть модуль, отвечающий за Ввод управляющих команд и данных (отсюда и название Controller — управление), и модуль отвечающий Вывод и представление информации о системе и о том что в ней происходит (View — Вид, представление).
Не смотря на простоту и очевидность этого принципа он часто нарушается. Сравните:
«Вид это то, что мы видим на экране ЭВМ, графические элементы»
Нет решаемой задачи, за определение взят вторичный признак.
Функциональное определение: «Вид отвечает за вывод информации о системе и ее представление для пользователя»
Четко указана решаемая задача, при этом абсолютно не важны детали ее реализации, то каким именно образом будет выводиться информация — графически ли, с помощью аудио или еще как-то. За счет чего и достигается универсальность.
«Контроллер обрабатывает действия пользователя, «interprets the mouse and keyboard inputs»
Указано действие, но не определена цель этого действия, то есть решаемая задача. Это равносильно тому, как сказать, что работа программиста заключается в том, чтобы нажимать клавиши на компьютере. В результате под «обработкой действий пользователя» может пониматься все что угодно, вплоть до бизнес логики. Плохо также то, что в определение вынесены детали реализации — мышь и клавиатура. Как только технология поменялась — мышь и клавиатура сменились сенсорными экранами, определение устарело)
Функциональное определение: «Контроллер обеспечивает удобный ввод команд и данных и их передачу от пользователя системе»
Но это теория, а было обещано, что вы увидите как деление Вид/Контроллер выглядело на практике. И для того чтобы это сделать нам потребуется небольшой экскурс в историю. Дело в том, что вначале термина CONTROLLER вообще не существовало. Вместо него Реенскауг использовал термин EDITOR (MODEL-VIEW-EDITOR) и писал о разделении пользовательского интерфейса на View и Editor.»The hardest part was to hit upon good names for the different architectural components. Model-View-Editor was the first set.»(Trygve Reenskaug). Попробую объяснить почему.
Не смотря на наличие технических возможностей (растровые экраны) во времена создания MVC и SmallTalk пользовательские интерфейсы преимущественно все еще оставались командными (Command-driven Interface) и представляли собой по сути обычные текстовые редакторы. В SmallTalk интерфейс так и назывался — Editor. И вот как он выглядел:
Можно сказать что Editor объединял в себе функции Контроллера и Вида. Он давал возможность относительно удобно вводить команды и данные (отображая нажатые клавиши и обрабатывая ввод с клавиатуры), и одновременно выводил информацию о выполнении команд и вообще о том что происходит в системе.
Лишь постепенно этот Editor преобразовывался в то, что мы сейчас привыкли понимать под GUI.
Вначале возникла простая, но весьма плодотворная идея — разделить единое окно на множество панелей. Парадигма многопанельности появились существенно раньше MVC (многопанельные браузеры точно присутствовали уже в Smalltalk-76) и была значительным продвижением сама по себе. Она до сих пор активно используется практически во всех текстовых интерфейсах.
Реенскауг ее, естественно, тоже использовал. Тут нужно иметь в виду, что Dynabook, вокруг которого в Xerox Parc создавались и smallTalk и графические интерфейсы и, в частности, MVC, задумывался как «детский компьютер». Поэтому стояла задача сделать работу с компьютерными программами доступной любому неподготовленному пользователю, в частности ребенку. Реенскауг исходил из того, что пользователь может вообще ничего не знать о программе и о том как она устроена. И для того чтобы он мог с программой взаимодействовать нужно каким-то образом отобразить ему основную информацию о системе и доменной модели, лежащей в ее основе, чтобы пользователь понимал с чем имеет дело.
Выводилась такая информация на отдельных панелях, которые собственно и стали называться View. Как правило доменная модель отображалась при помощи нескольких различных Видов:»MVC задумывался как общее решение, дающее возможность пользователям контролировать большие и сложные наборы данных… Он особенно полезен тогда, когда пользователю нужно видеть Модель одновременно в разных контекстах и/или с разных точек зрения.» И поскольку Реенскауг считал, что графическое представление информации нагляднее чем текстовое, то в основном виды у него представляли собой разного рода графики и диаграммы.
Далее. Так как пользователь ничего (или почти ничего) не знает о системе, а видит лишь всевозможные View, удобно и наглядно отображающие нужную ему информацию, то соответственно и управление системой должно было выглядеть так, как если бы пользователь управлял непосредственно самими View и тем что на них отображено. Поэтому для ввода команд стал использоваться не один «general Editor», а целое множество специализированных редакторов, каждый из которых был связан со своим Видом и был заточен на ввод команд (взаимодействие с доменной моделью) лишь в контексте этого Вида — permits the user to modify the information that is presented by the view.
Таким образом, если для специалиста интерфейсом обычно служил некий общий редактор, дающий возможность вводить в систему любые команды (но для этого нужно было штудировать мануалы, знать команды и понимать, как система работает), то для неподготовленного пользователя минимальным интерфейсом становится пара — View и связанный с ней специализированный Editor (который собственно и будет впоследствии переименован в Контроллер). Такой интерфейс предоставлял весьма редуцированный набор команд и возможностей, зато им можно было пользоваться без предварительной подготовки.
Первый доклад Реенскауга так и назывался: «THING-MODEL-VIEW-EDITOR. An Example from a planning system». В нем была представлена первая реализация MVC на примере системы планирования и управления неким большим проектом (своего рода task-manager). Доменной моделью являлась сеть (network) «активностей», которая описывала что должно быть сделано (активность), в каком порядке, за какое время, кто в каких активностях участвует и какие ресурсы для каждой активности требуются. Пример призван был показать что »одна Модель может быть отображена с помощью многих различных Видов» и вот как там выглядел пользовательский интерфейс:
Доменная модель отображалась с помощью трех различных диаграмм (Видов). Нужно отметить, что виды и у Реенскауга и в SmallTalk вовсе не были «пассивными», они самостоятельно обрабатывали относящиеся к ним действия пользователя, в частности позволяли делать скроллинг и выделение элементов:»A ListView has fields where it remembers its frame, a list of textual items and a possible selection within the list. It is able to display its list on the screen within its frame, and reacts to messages asking it to scroll itself.» Это важный момент и я еще к нему вернусь.
Первая диаграмма отображает множество активностей, относящихся к некоторому проекту/сети и связи между ними. Подобно тому, как список позволяет выбрать/выделить некий свой элемент, данная диаграмма позволяла выбрать некую активность. С диаграммой связан редактор, который дает возможность запрашивать у системы и редактировать информацию, относящуюся к выбранной активности. Например: длительность активности, кто в ней участвует, какие активности предшествуют и тп
Вторая диаграмма — GanttView (календарный или временной график Гантта) показывает расположение проекта и его активностей во времени. Эта диаграмма также позволяет выбрать некую активность. Редактор, связанный с GanttView дает возможность »to pass on operations on the network and its activities that are related to this particular View». В частности он позволяет изменить запланированную дату начала и окончания выбранной активности, а также осуществлять планирование и управление проектом/сетью как целым.
Третья диаграмма — это диаграмма ресурсов, требуемых для осуществления активностей, в зависимости от времени. Любопытно то, что с этой диаграммой связан не редактор, а список и в зависимости от того какая активность в этом списке выбрана, диаграмма ресурсов отображает только те ресурсы, которые относятся к выбранной активности. Такое сочетание двух видов, один из которых «управляет» отображаемой информацией другого характерно для многих интерфейсов. А идея использовать для «управления» не текстовый редактор, а список ляжет в основу большинства контроллеров в SmallTalk-80.
Термин Controller возник практически перед самым уходом Реенскауга из Xerox PARC »After long discussions, particularly with Adele Goldberg». И из-за этого оригинальные работы Реенскауга сложно читать, поскольку одним и тем же словом «Editor» он называет:
- Панели-редакторы которые связывались с соответствующими View для ввода команд и «управления View» и которые в Smalltalk-80 стали Контроллерами (»Smalltalk-80 Controller у меня назывался Editor»).
- Пару — Вид и связанный с ним Контроллер, которая стала базовым блоком для построения любых интерфейсов. В этих случаях Реенскауг пишет о разделении Editor на View и Controller
- Весь интерфейс целиком, который мог включать в себя несколько блоков Вид-Контроллер. И в этом случае Реенскауг пишет о первичном разделении приложения на Model (domain model) и Editor. Позже для обозначения таких сложно составных интерфейсов Реенскауг будет использовать термин Tool.
Также к обязанностям Контроллера Реенскауг относил управление самим интерфейсом и в частности множеством входящих в него Видов:»Controller was responsible for creating and coordinating its subordinate views». Соответственно иногда он пишет, что Контроллер это связь между пользователем и системой, а иногда что это связь между пользователем и Видами и что Контроллер передает команды Видам.
Тем не менее, как видно из примера, Реенскауговский «Editor-Controller» был вполне себе видим и имел графическое представление.
Посмотрим как дело обстояло в SmallTalk-80.
Первое: в SmallTalk-80 текстовые редакторы, которые Реенскауг использовал для ввода команд, «официально» стали Контроллерами.
»ParagraphEditor» и »TextEditor», обладающие стандартными функциями ввода и редактирования текста, в SmallTalk-80 являлись потомками класса »Controller»:
Поскольку, как уже упоминалось, редакторы объединяли в себе функции Ввода и Вывода, то они также использовались для отображения текстовой информации во многих Видах — TextView, TextEditorView.
Стив Барбек дает по этому поводу довольно подробное разьяснение:»Все контроллеры, которые принимают ввод с клавиатуры, являются наследниками «ParagraphEditor» в иерархии Контроллеров. «ParagraphEditor» предшествовал созданию парадигмы MVC. Он одновременно выполняет две функции — обрабатывает ввод текста с клавиатуры и отображает его на экране. Поэтому в некотором смысле он представляет собой нечто среднее между Видом и Контроллером. Виды, которые используют подклассы «ParagraphEditor» в качестве контроллера, полностью переворачивают стандартные роли — для того чтобы отобразить текст они посылают его своему контроллеру» [All controllers that accept keyboard text input are under the ParagraphEditor in the Controller hierarchy. ParagraphEditor predates the full development of the MVC paradigm. It handles both text input and text display functions, hence it is in some ways a cross between a view and a controller. The views which use it or its subcasses for a controller reverse the usual roles for display; they implement the display of text by sending controller display].
О причине подобных «парадоксов» я постараюсь написать дальше, пока же просто обратите на это внимание
Второе: в SmallTalk-80 с каждым Видом (подвидом) ОБЯЗАТЕЛЬНО был связан свой Контроллер, который давал возможность производить некие операции с той информацией, которую Вид отображает
Соответственно любое множество Видов (подвидов) входящих в состав пользовательского интерфейса на самом деле всегда сопровождалось точно таким же множеством связанных с ним Контроллеров. Опять таки у Стива Барбека этой теме посвящен целый раздел который так и называется — «Communication Between Controllers».
Третье: главное усовершенствование состояло в том, что для ввода команд преимущественно стала использоваться мышь, а не клавиатура.
В SmallTalk-80 помимо контроллеров-редакторов (ParagraphEditor и TextEditor) появляется MouseMenuController, который становится основным средством ввода команд. Пользовательские интерфейсы из Command-driven становятся Menu-driven (User Interfaces)
Доступные команды для каждого Вида формировались явно в виде списка. А связанный с Видом MouseMenuController предоставлял для их отображения и удобного ввода специальное графическое средство — всплывающие pop-up menu, которые появлялись при нажатии соответствующей кнопки мыши. Вот так они выглядели:
Повторю: pop-up menu относились к MouseMenuController. Меню являлись специальным графическим инструментом/средством для удобного отображения и ввода команд (определенных в контексте некоторого вида), который Контроллер, связанный с этим Видом, предоставлял пользователю. Вот что пишет Краснер:»Хотя меню можно рассматривать как пару вид-контроллер, но чаще всего они считаются входными устройствами и следовательно относятся к сфере контроллера… За создание всплывающих меню при нажатии какой-нибудь кнопки мыши отвечает класс MouseMenuController… По умолчанию PopUpMenus возвращают числовое значение которое вызвавший их контроллер использует для того чтобы определить какое действие ему нужно совершить… Из-за широкого использования всплывающих меню большинство контроллеров пользовательского интерфейса являются подклассами MouseMenuController».
Так что основной контроллер SmallTalk-80 (MouseMenuController) тоже имел свою графическую часть — pop-up menu, и разделение пользовательского интерфейса на Виды и Контроллеры стало выглядеть следующим образом (иллюстрация взята из статьи Гленна Краснера):
Меню относящиеся к MouseMenuController-ам можно видеть абсолютно во всех SmallTalk приложениях и примерах. Вот так с развернутыми меню выглядят уже упоминавшиеся Workspace и Inspector:
А вот так выглядит Browser, включающий в себя 5 Видов (подВидов) и соответствующих им Контроллеров
Предполагаю, что именно из-за широкого использования всплывающих меню Реенскауг и писал, что контроллер в smalltalk »это эфемерный компонент, который View создает при необходимости в качестве связывающего звена между View и входными устройствами такими как мышь и клавиатура».
Так что, можем развеять очередной миф:
Мифы: Все графические элементы пользовательского интерфейса относятся к Виду. Контроллер это исключительно логика обработки движений мыши, нажатий клавиш на клавиатуре и других входящих событий производимых пользователем.
На самом деле и в реализации Реенскауга и затем в Smalltalk-80 большинство Контроллеров имели »графическую составляющую, помогающую пользователю вводить команды и данные». И именно такие Контроллеры в основном использовались в пользовательских приложениях. Хотя, конечно же, были Контроллеры и без графической составляющей, но они в основном применялись для более низкоуровневых системных задач (об этом чуть позже).
Если просуммировать, то можно сказать что Контроллер это часть пользовательского интерфейса, которая отвечает за то чтобы 1) предоставить пользователю удобные средства для ввода команд и данных, а затем 2) действия пользователя перевести в вызовы соответствующих методов Модели и передать их ей.
Вот как определял Контроллер сам Реенскауг:»Контроллер это связь между пользователем и системой. Он предоставляет пользователю меню и другие средства для ввода команд и данных. Контроллер получает результат таких действий пользователя, транслирует их в соответствующие сообщения и передает эти сообщения» [ A controller is the link between a user and the system. It provides means for user output by presenting the user with menus or other means of giving commands and data. The controller receives such user output, translates it into the appropriate messages and pass these messages on].
Современные графические интерфейсы (GUI) для ввода команд используют весь спектр доступных средств: текстовые и графические меню, кнопки, всплывающие pop-up меню, всевозможные переключатели (как в реальных приборах), текстовые поля для ввода данных (TextEditor свелся к TextField). Все эти элементы служат в основном для управления, а не для отображения информации. По английски они так и называются — controls (элементы управления).
И если продолжить аналогию, то разделение Вид/Контроллер в современных системах выглядело бы примерно следующим образом:
Можно сказать, что Контроллер это «панель управления» (Control panel). А Вид это «обзорная панель» или «панель наблюдения за системой» включающая в себя текстовые описания, списки, таблицы, графики, шкалы, световые табло, и всевозможные индикаторы состояния.
Красота MVC заключается в том, что его идеи универсальны и применимы не только к информационным системам. Не важно прибор это или программа, в простейшем случае интерфейс, как правило, содержит блок/панель управления, позволяющий вводить команды — Контроллер, и блок отображения информации — Вид.
Реализация Видов и Контроллеров
Что нам это дает? Ну во первых, становится понятно что логика работы Вида никак не может быть помещена в Контроллер:
Если всю логику работы GUI вынести в Контроллер, то это нарушало бы сразу несколько принципов:
- главный принцип определяющий качество декомпозиции — High Cohesion + Low Coupling, который говорит что «резать» на модули нужно так, чтобы связи, особенно сильные, оставались преимущественно внутри модулей, а не между ними
- Принцип единственной ответственности (Single responsibility principle).
Второе. Тонкий Вид, рассматриваемый исключительно как набор графических элементов, не выполняет никакой функции и поэтому в плане пере-использования мало полезен. Если же мы рассматриваем Вид как полноценный функциональный модуль, решающий довольно общую и востребованную задачу — визуализация и удобное представление данных, то при правильном подходе он становится идеальным кандидатом на пере-использование.
В этом смысле разделение пользовательского интерфейса на Вид и Контроллер очень красивый шаг — Контроллер вобрал в себя большую часть зависимостей. Виду от Модели нужны лишь данные для отображения в определенном формате. Соответственно потенциально один и тот же Вид может быть использован для визуализации информации в разных приложениях.
Последнее время активно разрабатывается и используется концепция Dashboard-ов (информационных панелей), для которых создаются наборы универсальных виджетов, позволяющих наглядно и удобно визуализировать «все что угодно». В отличие от «тупого вида» такие »полноценные блоки визуализации» (инкапсулирующие свою логику, настройки и способные работать самостоятельно) очень востребованы и ценны сами по себе.
Когда Вид и Контроллер, трактуются как функциональные модули, отвечающие за решение определенных задач, то становится понятно, что для того чтобы инкапсулировать свою логику работы им вовсе нет нужды смешивать ее с графикой. Ведь любой модуль, при необходимости, может быть разделен на подмодули и обладать своей внутренней структурой.
Как мы выяснили основной Контроллер SmallTalk-80 — MouseMenuController вовсе не являлся «всего лишь обработчиком действий пользователя с мышью и клавиатурой», на самом деле он делал довольно много вещей:
- Задавал набор и названия команд, доступных пользователю в контексте некоего Вида,
- Определял логику того, как эти команды транслировать в вызовы соответствующих методов Модели
- Отображал доступные команды
- Обрабатывал низкоуровневые движения мыши и создавал высокоуровневые события, к которым удобно привязывать выполнение команд (Event Driven подход).
Для таких Контроллеров просто «просилась» MVC архитектура. И в SmallTalk-80 она была использована:»pop-up menu are implemented as a special kind of MVC class» (Краснер).
Тут важно понимать, что Модель в этом «внутреннем» MVC не имеет никакого отношения к доменной модели, это именно внутренняя вспомогательная модель, описывающая «состояние» самого контроллера (в частности то, какая команда выбрана) и логику изменения этого состояния.
Аналогично дело обстоит и с Видом.
Мифы: То, что Вид это всего лишь «графика», является такой же идеализацией как и то, что Контроллер это «исключительно логика».
Визуализация информации является непростой задачей, для решения которой, как правило, требуются дополнительные данные, характеризующие именно сам процесс отображения. Большинство Видов, используемых в реальных приложениях, это довольно сложные объекты со своим «состоянием» и логикой его изменения.
Например, Вид редко когда может отобразить всю Модель целиком, обычно он отражает лишь какую-то ее часть и ему необходимо «знать» какая именно «часть Модели» должна быть отражена в данный момент. Многие виды позволяют «выделять» какие-то элементы, а значить им где-то нужно хранить информацию о выделениях.
Где хранились такого рода дополнительные данные и логика их изменения? В принципе ответ очевиден и, если вы помните, Реенскауг ответил на этот вопрос — конечно же в самом Виде. Но! Не в перемешку с графикой, а в отдельном под-модуле/классе/скрипте, то есть в некоторой внутренней модели.
И раз есть вспомогательные внутренние модели, то должны быть и специальные Контроллеры, которые этими внутренними моделями управляют. Такие Контроллеры изменяют исключительно состояние самого Вида и не не имеют никакого отношения к Контроллеру приложения, изменяющему состояние доменной модели. Положением фрейма, например управлял ScrollController. То есть в общем случае Виды тоже имеют структуру MVC. Но пока отложим вопрос с Контроллерами и сосредоточится на главном — на внутренних моделях.
Фаулер такие внутренние модели, являющиеся частью представления называет — Presentation Model: Presentation Model pulls the state and behavior of the view out into a model class that is part of the presentation.
И вот тут внимание! В первой части статьи подробно рассказывается о том, как в MVC был «потерян» Фасад и что из-за этого его роль на себе вынуждены брать другие компоненты. Так вот хотя Фаулер и пишет что »Presentation Model is not a GUI friendly facade to a specific domain object» на практике PresentationModel, ViewModel, ApplicationModel не только описывают состояние и поведение представления, но одновременно являются еще и Фасадами к доменной модели.
В примере, который Фаулер подробно разбирает, хорошо видно, что его PresentationModel является именно смесью Фасада и модели представления. Ну, а Microsoft прямо пишет:»Presentation model class acts as a façade on the model with UI-specific state and behavior, by encapsulating the access to the model and providing a public interface that is easy to consume from the view» (MSDN: Presentation Model)
Безусловно такой подход противоречит Принципу единой ответственности, но он существует, используется во фреймворках и в принципе работает. Поэтому о нем стоит знать.
В отличие от него в Java подобного смешения стараются не допускать. В Java Swing «application-data models» и «GUI-state models» разделены гораздо более четко. Все, наверное, читали или слышали что Java Swing компоненты реализованы в виде MVC. И большинство наверняка уверены в том, что M в этой триаде это тот самый интерфейс к доменным данным. И по сути это правильно, почти…
Давайте внимательно посмотрим на список JList. У него действительно есть модель, обеспечивающая доступ к доменным данным — ListModel. Но кроме нее у списка имеется еще одна модель — ListSelectionModel, которая отвечает исключительно за внутреннюю логику выделения элементов списка (item selection). И вот эта модель как раз и является в чистом виде внутренней моделью представления — «GUI-state model»:
»The models provided by Swing fall into two general categories: GUI-state models and application-data models.
GUI state models are interfaces that define the visual status of a GUI control, such as whether a button is pressed or armed, or which items are selected in a list. GUI-state models typically are relevant only in the context of a graphical user interface (GUI).
An application-data model is an interface that represents some quantifiable data that has meaning primarily in the context of the application, such as the value of a cell in a table or the items displayed in a list. These data models provide a very powerful programming paradigm for Swing programs that need a clean separation between their application data/logic and their GUI» (см статью от создателей A Swing Architecture Overview).
У таблицы JTable помимо application-data модели, обеспечивающей доступ к доменным данным — TableModel, имеются целых две внутренних GUI-stateмодели — ListSelectionModel и TableColumnModel.
А вот у кнопки JButton имеется лишь GUI-state модель — ButtonModel. Что в принципе и логично. Кнопка не отображает доменных данных, это в чистом виде Контроллер с внутренней моделью, которая определяет состояние кнопки (нажата/не нажата).
При использовании графических компонент нам, в основном, приходится иметь дело с application-data моделями, через которые собственно и осуществляется взаимодействие домена и интерфейса. С GUI-state моделями мы сталкиваемся лишь тогда, когда возникает необходимость изменить дефолтное поведение компонента. Поэтому это нормально и правильно что многие даже не знают о наличие GUI-state моделей.
Видно, что в таблице некоторые модели отмечены одновременно и как GUI и как data. Особых пояснений не дается, написано что это зависит от контекста использования модели. Я могу высказать по этому поводу лишь предположение.
Все такого рода промежуточные модели относятся к компонентам, которые являются либо разновидностью скроллбара либо разновидностью переключателя (кнопка с состоянием). Такие компоненты предназначены прежде всего для управления и по сути представляют собой контроллер (в SmallTalk скроллбар и был контроллером — ScrollController), но с некоторым внутренним состоянием. Соответственно у таких компонент имеется лишь GUI-state модель и в дефолтной реализации состояние этих компонент никак не связано с состоянием доменной модели, а зависит лишь от действий пользователя, то есть от того, в какое состояние пользователь этот контрол/кнопку перевел.
Но при этом выглядит состояние таких контроллеров так, как если бы оно было согласовано с состоянием доменной модели: мы перевели переключатель в состояние «On», в доменную модель передалась соответствующая команда, там что-то включилось… и доменная модель тоже перешла в некое состояние «On». Достигается такая псевдо-согласованность как правило «сама собой», автоматически.
Тем не менее возможны ситуации когда может понадобиться настоящее реальное согласование доменной модели и состояния переключателя. В этом случае будет написана реализация ButtonModel, в которой метод isSelected () перестанет зависеть от действий пользователя с кнопкой, а вместо этого будет напрямую отображать состояние домена (некий его флаг). Кнопка при этом становится отчасти видом, а ButtonModel перестает быть внутренней GUI-state и становится application-data
В заключение темы привожу схемы обоих описанных тут подходов чтобы каждый мог выбрать то, что ему больше подходит:
Я стараюсь первого варианта избегать. Мое ИМХО что подобное смешение хоть и соблазняет своей кажущейся простотой, но на практике приводит к тому что PresentationModel превращается в большую «свалку». Представьте что есть реальный сложный интерфейс. Если использовать подход (2), то мы этот интерфейс разобьем на ui-модули, и каждый модуль будет инкапсулировать свою логику в виде небольшой внутренней ui-state модели. А вот если использовать подход (1), то логика работы всего этого большого интерфейса будет свалена в кучу вперемешку с логикой фасада в PresentationModel. Пока такая PresentationModel или ApplicationModel создается и управляется автоматически неким фреймворком все хорошо. Но если подобное писать ручками… то мозг начинает ломаться и часто это приводит к тому, что логика представления рано или поздно просачивается через фасад в доменную модель. Даже у такого гуру как Фаулер, в его детском примере это таки произошло (интересно, кто-нибудь еще заметил это место?). В архитектуре, где фасад и GUI-state модели разделены, вероятность такого рода ошибок значительно ниже. Кто хочет развлечься, то в том же примере у Фаулера можно найти ненужное копирование данных, о котором писалось в первой части.
Объединенный ВидКонтроллер. «Упрощенный MVC»
Раз уж мы коснулись Java Swing, то нужно сказать еще об одной его важной особенности — в отличие от SmallTalk-80 где и скроллбар и pop-up меню были реализованы в виде полноценного MVC (с внутренней моделью, внутренним низкоуровневым контроллером и видом) Swing в реализации базовых gui компонент использует «упрощенный MVC», в котором Вид и Контроллер объединены в единый компонент, который одновременно отображает данные и обрабатывает действия пользователя. Обычно он так и называется: объединенный ViewController или UI-object, IU-delegate. Вот еще одна статья, где это подробно описывается: MVC meets Swing.
Самое интересное и неожиданное заключается в том, что такой «упрощенный MVC» де-факто используется в большинстве GUI библиотек и фреймворках пришедших на смену SmallTalk-80: VisualAge Smalltalk от IBM, Visual SmallTalk, VisualWorks SmallTalk, MacApp… (Smalltalk, Objects, and Design стр 124–125).
Фактически классический вариант MVC, с обязательным разделением Видов и Контроллеров, особенно на низком уровне, только в SmallTalk-80 и был реализован. Почему? Опять таки я могу высказать лишь предположение.
Основное отличие SmallTalk-80 о