Материал: Портянкин И. Swing

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

Основные концепции

15

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

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

инастраивать его внутренние механизмы (а это всегда нелегко, особенно с компонентами от других производителей). Был у такого подхода и еще один недостаток. Создавая компонент, его разработчик, сознательно или нет, стремился приблизить его вид и поведение к виду и поведению компонентов той платформы, на которой он работал. Получалось, что большинство библиотек имели Windows-подобные компоненты. Они так выглядели и на Unix, и на Mac, и работать с ними пользователям этих систем было крайне неудобно.

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

ив программировании библиотек пользовательского интерфейса. Разработчики Swing обратились к решению, проверенному годами. Посмотрим, что это за решение.

Архитектура MVC

Довольно давно (по меркам компьютерной индустрии вообще в доисторических временах), около 1980 года, появилась очередная версия объектно-ориентированного языка Smalltalk, названная Smalltalk-80. В этой версии возникла архитектура, предназначенная для создания легко расширяемых и настраиваемых пользовательских интерфейсов. Эту архитектуру назвали модель — вид контроллер (Model/View/Controller, MVC). По сути дела, появление в Smalltalk данной архитектуры привело к возникновению пользовательского интерфейса таким, каким мы его знаем сегодня, ведь именно тогда родились основные концепции и внешний вид большинства известных компонентов. Идея этих компонентов затем была использована в Macintosh, после чего перешла к многочисленным последователям Macintosh. Несмотря на то, что с момента появления MVC прошло уже немало времени, эта архитектура остается одним из самых удачных объектно-ориен- тированных решений, и поэтому часто используется и сегодня.

Как нетрудно догадаться по названию, MVC состоит из трех частей.

Модель (model) хранит данные компонента и позволяет легко, не обращаясь к самому компоненту, изменять или получать эти данные. Например, раскрывающийся список позволяет вывести на экран перечень элементов (обычно это строки). Вместо того чтобы включать методы для манипуляции элементами списка в класс раскрывающегося списка, можно предоставить отдельный класс, работающий исключительно с данными. Такой подход позволит разработчику сосредоточиться именно на той задаче, которой он занимается в данный момент: можно сначала подготовить данные (считать их из файла или сетевого соединения, отсортировать, локализовать и т. п.), а потом уже передать их раскрывающемуся списку для вывода на экран. Хранение данных отдельно от самого компонента также позволяет изменять структуру данных модели, не меняя функций компонента. Самое же главное состоит в том, что отдельная модель позволяет появляться «хорошим» классам, которые описывают данные в языке реша-

16

ГЛАВА 1

емой задачи, а не в языке той или иной библиотеки пользовательского интерфейса. Если нам понадобится список книг в библиотеке, мы назовем класс именно так, а не будем встраивать его в мудреные классы, подгоняя под нужды интерфейса.

Вид (view) выводит данные на экран для представления их пользователю. Отделение вида от данных позволяет представлять одни и те же данные совершенно разными способами. Например, текст формата HTML (Hypertext Markup Language — гипертекстовый язык разметки) можно вывести в разном виде: провести разметку документа, разместить изображения и ссылки, использовать различные шрифты, а можно показать HTML-документ как код, который состоит из набора тегов и текста среди них. Между тем данные для этих разных видов требуются одни и те же (текст формата HTML). Вспоминая пример с раскрывающимся списком, можно сказать, что он является видом, представляющим на экране набор элементов. Данные раскрывающегося списка можно было бы представить и в другом виде, например, в таблице.

Контроллер (controller) определяет, как должны реагировать вид и данные модели в ответ на действия пользователя. Наличие в MVC контроллера позволяет использовать одни и те же данные и виды в разных целях. HTML-страница, например, может быть показана в браузере или в визуальном средстве создания страниц. Браузер может задействовать контроллер, который при щелчке на ссылке переходит на страницу, указанную в ссылке (полностью меняет данные модели, загружая в нее новую порцию HTML-текста), а визуальное средство, скорее всего, использует контроллер, вызывающий при щелчке на ссылке редактор свойств этой ссылки (который меняет лишь часть данных модели, относящихся к ссылке). Раскрывающемуся списку также не помешает пара контроллеров: один для списка, не позволяющего редактирование элементов, а другой для редактируемого списка. Не всегда, но, тем не менее, довольно часто, контроллер также выполняет надзирательные функции и не позволяет пользователям портить модели некорректными данными, проводя проверку их на правильность перед отсылкой в модель.

Окончательно прояснит работу MVC и взаимодействие трех участников этой архитектуры рис. 1.3.

Щелчки мышью, нажатия кнопок

 

Контроллер

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Соответствующее

 

 

 

 

 

 

изменение вида

 

 

Вид

Изменения данных

 

 

 

 

 

 

 

Оповещение об

 

 

 

 

 

 

модели

 

 

 

 

 

 

 

 

 

 

 

 

 

 

изменениях

 

 

 

 

Обмен

 

 

 

 

 

 

 

 

 

 

 

 

 

данными

 

 

 

 

 

 

 

 

 

 

Модель

Рис. 1.3. Взаимодействия между моделью, видом и контроллером

Основные концепции

17

Пользователь взаимодействует с программой, совершая различные действия (нажимая клавиши, перемещая мышь, щелкая ее кнопками и т. п.); информация о его действиях поступает в контроллер. Контроллер определяет, как обработать эти действия,

ипосылает сообщения модели и/или виду8. При этом используются следующие обозначения: сплошные стрелки — это обращение к конкретному объекту с известным типом, а пунктирные стрелки — это оповещения9 заранее неизвестных объектов об изменении ситуации в системе. Предпочтительнее задействовать механизм оповещения (пунктирные стрелки), так как он позволяет избежать сильной связи между объектами, а это повышает гибкость системы. Особенно важен механизм оповещения для модели, что

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

Кроме такого, наиболее естественного способа работы с компонентом (все изменения происходят в ответ на действия пользователя), можно непосредственно взаимодействовать с отдельными частями архитектуры MVC. Иногда это может быть очень кстати. Например, вы можете использовать данные модели, не затрагивая контроллер и вид, манипулировать ими, и все изменения, что в них происходят, автоматически появятся на экране. Можно не ждать событий от пользователя, а программно генерировать их в контроллере (реакция на них со стороны модели и вида будет такой же) — это может быть весьма удобным при автоматизированном тестировании интерфейса или при обучении. Очень сильной стороной архитектуры MVC является также ее динамичность: никто не запрещает вам менять виды, контроллеры и модели прямо во время работы программы. Одним словом, архитектура MVC совсем не тривиальна, и возможности ее действительно велики.

Споявлением в Smalltalk архитектуры MVC стало возможным создавать очень гибкие пользовательские интерфейсы. У программиста имелся набор различных видов, моделей и контроллеров, используя их в нужном сочетании, можно было добиться практически любого эффекта. Упрощено было и создание новых элементов пользовательского интерфейса: функции моделей, контроллеров и видов были четко разделены, их легко было наследовать от уже существующих классов, немного изменяя реализацию в соответствии со своими нуждами.

Как вы помните, перед создателями Swing стояла задача отделения внешнего вида компонентов от их функций, так чтобы было возможно легко изменять внешний

8 Не удивляйтесь, контроллер может взаимодействовать и с моделью, и с видом, и сразу с обоими. Как мы увидим далее, контроллер — на самом деле довольно запутанная часть MVC, и поэтому от него зачастую просто-напросто избавляются. К примеру, с видом контроллер может взаимодействовать в современных интерфейсах с обратной визуальной связью, которая служит лишь эстетическим целям и удобству, но никак не связана с моделью.

9 Механизм оповещения, использованный в MVC, — это ни что иное, как шаблон проектирования под названием наблюдатель (observer), один из самых известных, самых простых и самых полезных шаблонов в мире объектно-ориентированного проектирования (все гениальное по-преж- нему просто). Мы подробно рассмотрим этот шаблон в главе 2, посвященной системе событий Swing, которая фактически представляет собой реализацию этого шаблона.

18

ГЛАВА 1

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

Все ли так хорошо в MVC?

Ответ кажется очевидным: «Конечно, все просто замечательно!». Действительно, архитектура MVC хороша — гибкая, легко настраиваемая, расширяемая и простая в понимании, она позволяет делать многое. Однако хоть это и кажется удивительным, использовать ее в оригинальном варианте оказывается не совсем удобно. Проблема здесь кроется не только и не столько в самой архитектуре — скорее накладывают свои ограничения те задачи, которые мы хотим решить с ее помощью, и те инструменты, которыми мы эти задачи собираемся решать.

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

икак эти данные меняются. Например, нажатие клавиши для текстового поля означает вставку символа в документ, для кнопки — изменение состояния на нажатое, для раскрывающегося списка — выбор нового элемента. Каждый раз контроллер меняет довольно специфичные данные модели, и основное преимущество от использования его в качестве отдельного элемента системы — возможность заменять одни контроллеры другими — просто теряется (как поведет себя контроллер кнопки в таблице?).

То же самое можно сказать и о связи контроллера и вида. Эта связь сильная: вид хранит ссылку на контроллер, а контроллер на вид. Можно попытаться избежать подобной связи, полагаясь только на связь контроллера с моделью и дальнейшее оповещение моделью вида. Но такое взаимодействие сделает интерфейс неудобным: оно практически не в состоянии учитывать некоторые операции при изменении данных, которые также нужно отображать: например, модель текстового документа не должна во избежание излишней сложности хранить информацию о мерцающем курсоре, выделенном тексте, операциях перетаскивания и других подобных вещах. Все это должны решить между собой контроллер и вид, и без отражения подобных операций интерфейс будет крайне неудобным. В итоге получается, что хотя формально MVC отделяет контроллер, последний фактически «намертво» привязан к определенному виду и модели этого вида. Если компонент предполагает несколько сложных реакций на действия пользователя (к таким компонентам можно отнести, например, текстовые компоненты), контроллер в той или иной форме можно оставить, в противном случае он лишь вносит дополнительные сложность и путаницу.

Решение напрашивается само собой — надо просто соединить в единое целое контроллер и вид, образовав визуально-поведенческую (look and feel) часть компонента. Это целое и будет общаться с моделью. Такой подход не просто больше подходит для Java, но еще и более удобен для программирования: например, если компонент нужно отключить, то логичнее вызвать какой-то метод компонента (который сразу отключит обработку событий и сменит внешний вид), а не тасовать контроллеры

ивиды.

Кстати, описанная в этом разделе проблема с контроллерами характерна не только для Java, но и для большинства других языков, и уже довольно давно. Многие библиотеки пользовательских интерфейсов используют MVC, но немногие реализуют каноническую архитектуру. Большинство объединяют контроллер и вид, чтобы упростить библиотеку и улучшить взаимодействие ее частей. Более того, еще в самой первой версии

Основные концепции

19

MVC в языке Smalltalk-80 применялось их объединение, называемое инструментарием (tool) для поддержки модели. Впрочем, есть и библиотеки, строго разделяющие контроллер, модель и вид.

Решение Swing — представители пользовательского интерфейса

Итак, мы выяснили, что классическая архитектура MVC не очень хорошо подходит для Java: разделение контроллеров, моделей и видов не совсем оправдано. Гораздо более простым в реализации и дальнейшем использовании оказывается решение, совмещающее вид и контроллер в одно целое. Именно такое решение было выбрано разработчиками Swing. Посмотрим, что у них получилось (рис. 1.4).

Щелчки мышью, нажатия кнопок

Контроллер

Представитель UI

(UI delegate)

Вид

Изменение данных

модели

Оповещение об изменениях

Модель

Рис. 1.4. Организация представителей

Как видно из диаграммы, разработчики Swing объединили вид и контроллер в новый элемент, который назвали представителем (delegate) пользовательского интерфейса (User Interface, UI). Теперь все действия пользователя поступают не в контроллер, определяющий реакцию на них, а в этот новый элемент, в котором происходит значительная часть работы. Он определяет, нужно ли реагировать на них (так как контроллер теперь находится внутри, исчезает необходимость переделывать его, чтобы отключить реакцию на действия пользователя — свойство «включено/выключено» стало свойством представителя) и, если нужно, то сразу же без генерации каких-либо событий и изменения данных меняет вид (это происходит быстро — вид и контроллер находятся в одном месте и имеют исчерпывающую информацию друг о друге, благодаря этому пользовательский интерфейс быстро реагирует на любые изменения и позволяет легко воспроизвести самые сложные операции по изменению данных), а уже после этого представитель говорит модели о том, что данные изменились, и их необходимо обновить. Модель обновляет хранящиеся в ней данные и оповещает заинтересованных субъектов (чаще всего того же представителя) об изменениях. В ответ внешний вид компонента окончательно обновляется, чтобы соответствовать новым данным.

Если рассматривать диаграмму подробней, оказывается, что схема работы новой системы очень проста, но весьма эффективна. Отдельного контроллера теперь нет, и ему не

Источник: https://studfile.net/preview/15935977/