Инфраструктура Модель-Визуализация-Презентатор
(Model-View-Presenter Framework)
Перевод: Дмитрий Замоткин
Большинство программных средств разработки предлагают программисту инфраструктуру приложения, на основе которой может быть создано приложение. Современные графические пользовательские интерфейсы позволяют конечному пользователю с большой производительностью взаимодействовать с приложением и постепенно увеличивать сложность кода интерфейса. Инфрастуктура позволяет уменьшить накладные расходы по этой сложности за счёт заранее составленных классов с уже заложенной базовой функциональностью. Программист может пошагово добавлять функциональность для собственного приложения, создавая подклассы или комбинируя некоторым образом уже существующие классы. Наличие инфраструктуры в значительной степени уменьшает количество требуемого нового кода.
Dolphin Smalltalk использует уникальную инфраструктуру Модель-Визуализация-Презентатор или МВП (MVP). Во многом она схожа с другими архитектурами, основанными на компонентах, но мы убеждены, что её гибкость во многом превосходит большинство известных конкурентов. Конечно, можно предположить, что гибкость также увеличивает и сложность, и это правда, но исключительные возможности повторного использования кода значительно перевешивают дополнительное время, потраченное на начальное изучение.
Несмотря на то, что Вы можете создавать приложения в Smalltalk не основываясь на Модель-Визуализация-Презентатор (например, как в пособии "Эксперименты с простым окном"), Вы обнаружите, что все средства разработки в Dolphin Smalltalk построены на этой инфраструктуре. Ниже мы обсудим различные элементы МВП, в качестве примера используя средства разработки, чтобы Вы могли увидеть, как работает МВП на практике.
Когда Вы будете готовы создать своё собственное приложение с помощью МВП, следуйте шаблонам Приложение и Модель-Визуализация-Презентатор из библиотеки шаблонов.
Модель-Визуализация-Презентатор
Приложения, построенные на МВП, состоят из триады сотрудничающих классов.
Как Вы видите, каждая триада состоит из трёх элементов: модель, визуализация и презентатор.
Модель
Как правило, модель - объект доменного уровня, или иначе бизнес-объект. Он содержит данные приложения и обеспечивает согласованные методы доступа к ним. Типичными классами модели могут быть InsurancePolicy (страховой полис) или PersonalAccount (личный счёт). Классу модели совсем не обязательно поддерживать некий предопределённый протокол сообщений, поэтому нет необходимости делать модель подклассом какого-то суперкласса. Однако, иерархия классов Dolphin Smalltalk содержит класс Model, который Вы можете выбрать в качестве предка, главным образом для идентификации.
Пример: взгляните на класс SmalltalkSystem, который является моделью, используемой в Dolphin Smalltalk для представления среды разработки. Он содержит методы, необходимые для выполнения операций в инструментах разработки и большинство этих инструментов используют экземпляр-одиночку (singleton) класса SmalltalkSystem в качестве модели.
В модели не должно быть никаких связей с пользовательским интерфейсом. Это позволяет повторно использовать её в различных ситуациях при совершенно разных требованиях к пользовательскому интерфейсу. Следовательно, модель не знает о существовании каких-либо визуализаций или презентаторов триады. Фактически это означает, что программист не должен создавать переменные экземпляра в модели, содержащие ссылки на визуализации, презентаторы или другие объекты пользовательского интерфейса.
Визуализация
Визуализация - это окно, которое отвечает за отображение пользователю данных модели. Это, как правило, но не всегда элемент управления Windowsä. На приведённой ниже диаграмме красная цветом указана косвенная связь между моделью и визуализацией, тем самым визуализация является Обозревателем для модели. Как мы уже упоминали, модель не должна содержать прямой ссылки на визуализацию, но Обозреватель должен знать об изменении модели, что реализуется посредством уведомлений о событиях (используя метод #trigger:). Если мы зарегистрируем визуализацию на интересующее её событие (используя метод #when:), то после запуска события метод, его перехватывающий, выберет обновлённые данные модели и покажет их пользователю. То, что визуализация знает о своей модели, но не наоборот, является фундаментальным положением МВП.
Пример: Взгляните на метод #model: класса BasicListAbstract. Вы увидите, что при подключении к нему модели этот абстрактный суперкласс визуализации Listbox регистрирует несколько интересующих событий модели. На событие #listChanged регистрируется метод #onListChanged, который, как Вы видите, обеспечивает обновление содержимого визуализации.
Визуализация имеет прямой доступ к переменным экземпляра, как модели, так и презентатора.
Презентатор
В то время как визуализация отвечает за передачу пользователю информации о модели, обязанностью презентатора является воздействие на модель на основе вводимых пользователем данных. Действия пользователя (выделение в списке, перемещение мыши, нажатие на клавиатуре) принимаются визуализацией и передаются в презентатор. Обычно все действия пользователя, о которых визуализация сообщает презентатору, непосредственно связаны с изменением данных модели. Но иногда визуализация подготавливает низкоуровневые действия в высокоуровневые команды перед уведомлением презентатора. Например, при перетаскивании или генерации команды в вертикальном меню.
Презентатор может знать как о визуализации, так и о модели и играет роль посредника между ними.
Поскольку визуализация и презентатор регистрируются лишь на ограниченное количество событий, ожидаемых от модели, Вы не можете присоединить любую визуализацию или любой презентатор к любой модели, рассчитывая, что всё будет работать. Мы должны определить "тип" класса модели неким протоколом сообщений, включая протокол уведомлений о событиях. Мы могли бы назвать эти протоколы соответственно входным и выходным (in-protocol и out-protocol). Поэтому важно присоединять данную визуализацию или данный презентатор только к определённому типу модели. Например, классы ListBox и ListPresenter ожидают модель типа "список" с входным и выходным протоколами аналогичными классу ListModel.
В общем, и вполне небезосновательно, Вы можете подключать друг к другу только модель, визуализацию и презентатор специально разработанные для совместной работы. Полоса пропускания между моделью, визуализацией и презентатором (т.е. размер их входного и выходного протоколов) важна и обратно пропорциональна гибкости элементов. Чем проще Вы сделаете протокол, тем более гибким он будет, а вместе с тем и повысится взаимозаменяемость элементов. В качестве иллюстрации взгляните на случаи использования протокола ValueModel.
Архитектура, построенная на компонентах
Каждая МВП-триада может рассматриваться как компонента. Которая может быть повторно использована для создания новых составных компонент, которые, в свою очередь, могут быть использованы в других компонентах и т.д. Фактически, этот блок из трёх элементов - модель, визуализация и презентатор - очень схож с более традиционными компонентными архитектурами, такими как ActiveXä или Java Beansä. Выгодность компонентной архитектуры заключается в простоте соединения различных компонент без написания большого количества кода для их совместной работоспособности.
Компоненты МВП в Dolphin Smalltalk также позволяют это, но имеют дополнительное преимущество в индивидуальной взаимозаменяемости каждого элемента. Эта особенность дает Вам возможность быстро создавать новые компоненты простым присоединением различных комбинаций моделей, визуализаций и презентаторов. Количество возможных комбинаций ограничивается лишь "типом", который ожидает элемент (см. описание выше).
Сменные визуализации
В МВП все элементы являются сменными (pluggable). Если Вы желаете заменить визуальное представление компоненты Вам достаточно лишь присоединить другую разновидность визуализации к презентатору. Например, у Вас есть возможность подключить визуализацию "Multiline text" (Многострочный текст) к экземпляру TextPresenter (Презентатор Текста), заменив стандартную визуализацию Single line text" (Однострочный текст).
Попробуйте выполнить следующие примеры.
TextPresenter show.
Тем самым Вы создадите компоненту TextPresenter и отобразите присоединенную к ней визуализацию TextEdit. Визуализация для удобства сразу будет автоматически обрамлена окном верхнего уровня. Вы заметите, что ввести перевод строки в данной визуализации невозможно, поскольку TextEdit поддерживает только одну строку текста.
Совет: Кстати, если Вам интересно знать, как определяется визуализация по умолчанию для презентатора - взгляните на метод класса #defaultView. Вы увидите, что в метод defaultView класса TextPresenter (TextPresenter class>>defaultView) содержит строку 'Single line text', которая является именем ресурса для экземпляра TextEdit. Вы можете просмотреть или отредактировать эту визуализацию открыв Браузер Ресурсов и дважды щелкнув на 'TextPresenter - Single line text'.
Также Вы можете открыть другие визуализации вместе с TextPresenter.
TextPresenter show: 'Multiline text'
.
Теперь Вы, как и могли ожидать, способны вводить многострочный текст.
Другой эксперимент, на этот раз с BooleanPresenter (Презентатор Истины/Лжи):
BooleanPresenter show.
BooleanPresenter show: 'Push to toggle'.
Сменные модели
Если Вы желаете изменить способ хранения данных в компоненте, модель должна быть заменена или перестроена. Например, ListPresenter (Презентатор Списка) создается и присоединяется по умолчанию к ListModel (Модель Списка). При взгляде на класс ListModel Вы поймете, что он может быть обернут вокруг любой последовательной коллекции. Работой ListModel является перенаправление запросов лежащей в основе коллекции, с тем, чтобы запускались соответствующие уведомления об изменениях.
Совет: Модель по умолчанию для ListPresenter определена в методе класса #defaultModel. Если Вы посмотрите на метод ListPresenter class>>defaultModel, то увидите, что он возращает ListModel созданную на Упорядоченной Коллекции (OrderedCollection).
Итак, давайте создадим обычный ListPresenter и заполним его.
lp:= ListPresenter show.
lp model addAll: Object methodDictionary
keys.
Однако также возможно создать ListPresenter на модели, отличной от значения по умолчанию:
lp:= ListPresenter showOn: (ListModel
with: SortedCollection new).
lp model addAll: Object methodDictionary
keys.
В вышеприведенном примере названия методов в списке появятся упорядоченными, что обеспечивается классом SortedCollection.
Почему визуализации являются Обозревателями?
До настоящего времени мы рассматривали ситуации, в которых существует лишь одна визуализация для одной модели. Поэтому резонным может быть вопрос: почему мы сделали визуализации Обозревателями над моделями и не является ли излишним механизм оповещения об изменениях? Очевидно, что это более сложно, нежели прямое связывание, но существует ряд определенных преимуществ:
- То, что модель никогда не посылает сообщения напрямую визуализации, а визуализация обязана зарегистрироваться на уведомлениях от модели, придает визуализации определенную гибкость в выборе обрабатываемых уведомлений. Это поможет Вам, если вы решите подсоединить принципиально разные визуализации к одной и той же модели.
- У нас может быть более одной визуализации на любой модели.
Разделяемые модели
Поскольку мы используем шаблон Обозреватель как часть МВП, то получаем дополнительное преимущество: компоненты могут разделять данные модели. Обычно при создании презентатора тот ассоциируется с моделью по умолчанию, что наиболее эффективно при работе с данными модели. И это применимо для большинства случаев. Однако, Вы можете захотеть использовать два презентатора для отображения/редактирования тех же данных и каждый из них должен содержать актуальные данные, изменяться синхронно. Для этого оба презентатора могут быть скреплены с предварительно созданной моделью или один из презентаторов может разделять свою модель с другим.
В этом случае каждая визуализация знает об измениях, внесенных в другой визуализации или некоторой другой компоненте. Нижеприведенный пример демонстрирует это. Лучше, если Вы будете исполнять каждую строчку отдельно и изменять размеры окон с тем, чтобы они не заслоняли друг друга.
m := false asValue.
BooleanPresenter showOn: m.
BooleanPresenter show: 'Push to toggle'
on: m.
BooleanPresenter show 'Yes-no text' on:
m.
Базовые и Составные МВП компоненты
Существуют два принципиально различных типа МВП компонент, с которыми Вам придется столкнуться. В Базовой МВП Компоненте единственная модель соединена с единственным презентатором и единственной визуализацией. Из базовых триад, как простейших компонент, строятся составные компоненты.
Возьмем в качестве примера базовую компоненту TextPresenter. Она связана с моделью по умолчанию ValueHolder (Держатель Значения), обернутой вокруг String (Строки) и визуализацией по умолчанию TextEdit (Текстовый Редактор).
В Составной МВП компоненте (обычно) сложная модель присоединена к составному презентатору и составной структуре визуализаций. Часто модель, присоединенная к составному презентатору рассматривается как несколько под-моделей, каждая из которых связана со своим под-презентатором и под-визуализацией. В результате составная триада неизбежно состоит из иерархии под-триад, каждая из которых может быть как базовой, так и составной.
Хорошим примером составной компоненты является MethodBrowser. Моделью MethodBrowser'а является синглтон класса SmalltalkSystem, а визуализация построена в Редакторе Визуализаций (View Composer). Однако этот составной компонент включает в себя два дополнительных. ListPresenter (связанный с ListModel) используется для отображения списка методов, исходный текст выделенного метода показывается с помощью презентатора SmalltalkWorkspace.
Совет: На самом деле SmalltalkWorkspace достаточно необычная компонента. Для большей ясности на рисунке выше она показана связанной с моделью, по всей видимости String (чтобы хранить текст метода), с обёрнутой вокруг неё ValueModel. Однако в действительности у презентатора SmalltalkWorkspace в модели не содержится текста метода, только лишь в визуализации. Это сделано из соображений эффективности, для того чтобы уберечься от хранения копии большого количества текста в образе Smalltalk. Настоящей моделью для SmalltalkWorkspace тоже является синглтон SmalltalkSystem.
МВП обладает лучшим потенциалом повторного использования кода
Компонента MethodBrowser - хороший пример гибкости МВП по сравнению с другими компонентными архитектурами. С одной стороны она используется как часть MethodBrowserShell (визуалиция MethodBrowserShell.Default view). С другой стороны она включена в ClassBrowserShell с совсем другой визуализацией (ClassBrowserShell.Default view), которая даже не обладает прямоугольной формой. Иллюстрация на рисунке ниже.
Презентаторы основаны на классах, визуализации - на ресурсах
В МВП всякий раз, когда вы создаёте компоненту, вы всегда создаёте новый класс презентатора. Это истинно как для простых, так и составных презентаторов. Вся функциональность приложения основана на написании кода Smalltalk, который содержится в новом классе. Стало быть, мы можем сказать, что презентаторы в МВП основаны на классах.
Однако визуализации отличаются от презентаторов. Когда вы указываете какую-то визуализацию для презентатора вы указываете имя ресурса (файла), который загружаясь становится экземпляром визуализации, с ним и связывается презентатор. Экземпляр визуализации обычно создаётся с помощью Редактора Визуализаций и из него сохраняется как ресурс. Следовательно, мы можем сказать, что визуализации основаны на ресурсах.
Добавление основных ресурсов визуализаций
Как мы уже ни раз видели, View Composer это самый эффективный способ для создания составных визуализаций и для сохранения их как ресурсов. Тем не менее вы можете увидеть, что базовые визуализации в инструментах View Composer (такие как 'Single line text' или 'Check box') должны получаться каким-то другим способом. Действительно, элементарные ресурсы визуализаций таких как эти создаются посылкой сообщения #makeResource:inClass: любому выбраному классу визуализации.
Совет: Ресурсы визуализаций это объекты, которые проходят потоком через STB Filer. Обычно они помещаются в ByteArray (Массив Байтов) в образе Smalltalk, но есть возможность экспортировать их в обычный двоичный файл для последующего импорта.
Сравнивая МВП с МВК
Ранние среды разработки Smalltalk использовали в качестве фреймворка Модель-Визуализацию-Контроллер или МВК. Несмотря на большое сходство МВП и МВК, фреймворк в Dolphin Smalltalk более приспособлен к современным операционным системам, таким как Microsoft Windows. Дальнейшее обсуждение разницы смотрите в Model-View-Controller.