VisualData

Единство формы и содержания

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


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

Общая концепция

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

Так любое Множество интуитивно представляется в виде списка или таблицы, пользовательское Событие принимает форму кнопки, численное или текстовое Значение закономерно выглядит как текст в поле ввода/вывода, а логическое Значение привычно ассоциируется с чек-боксом.

Сразу обратим внимание, что у отдельной сущности, как правило, есть несколько форм ее представления. Это обусловлено как вариативностью вида и способа визуального отображения, так и видовым различием внутри домена сущности (типизация), а также различием в типах интерфейса. Ведь помимо визуального, существуют интерфейсы печатных форм (отчеты), файловые интерфейсы и интерфейсы обмена данными. Рассматривать мы будем исключительно визуальный интерфейс, но все сказанное будет равно применимо и ко всем прочим интерфейсам.

В объектной СУБД данные представлены объектами и значениями, которые являются экземплярами, производными от таких абстрактных сущностей понятийного уровня предметной области как классы данных и атрибуты классов. Классы данных и атрибуты классов совместно с абстрактными же сущностями связи — отношениями классов и связями атрибутов, совокупно образуют модель приложения. При этом, что примечательно, все сущности модели приложения в свою очередь сами являются декларативными экземплярами, производными от базисных сущностей еще более высокого уровня абстракции — уровня мета-определений. На этом уровне для каждой отдельной базисной мета-сущности: мета-класса, мета-атрибута, мета-отношения, мета-соединителя, мета-сокета и мета-кортежа, заранее определены все необходимые формы ее интерфейсного представления, в дальнейшем именуемые мета-представлениями.

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

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

Базовые сущности

Стоит сразу обратить внимание на тот факт, что сущности связи: отношение классов и связь атрибутов, не являются предметом отображения или пользовательского воздействия, и не участвуют в создании интерфейса. Вместе с тем, две других сущности: Класс и Атрибут, обладают ролями-аспектами, претендующими на "интерфейсную" самостоятельность. Это абстрактное Событие, выражающее собой интерактивно-функциональный аспект родительской сущности, и абстрактное Множество, отражающее ее количественный аспект. Таким образом, набор базисных сущностей уровня мета-определений дополняется мета-событием и мета-множеством, с их собственными мета-представлениями. Отметим, что в отношении События, Множество является такой же родительской сущностью, как Класс и Атрибут.

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

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

Представление Класса

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

Форма класса может представлять или единственный объект класса — объектная Форма, или множество объектов класса — списковая Форма. Естественно предположить, что неотъемлемой частью списковой Формы является представление Множества.

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

Представление Атрибута

В модели приложения Атрибут выполняет функции фабрики значений. Соответственно, представление Атрибута априори включает в свою интерфейсную часть способ ввода значения — так называемый Редактор значения. Наличие у Атрибута функциональных свойств также предполагает, что в сценарии приложения Атрибут может быть представлен не только как значение, но и как Событие.

Конкретное представление Атрибута как значения однозначно определяется типом, назначенным атрибуту при его создании. Представления типов Logical, String, а также Numeric, и всех его производных, имеют идентичную внутреннюю организацию и поведение, а их внешний вид — Поле ввода/вывода (check-box — его частный случай) очевиден и без комментариев.

Атрибуты отношения (домен User Defined Types) типизированы целевым классом отношения, что придает им соответствующую "ссылочную" специфику представления: атрибут отношения может быть представлен как сам целевой Класс, как один из его Атрибутов, или как Событие этого класса, функциональное или интерфейсное. В данном случае под интерфейсным понимается Событие, открывающее в новом окне назначенную событию Форму целевого класса.

Дополнительную специфику атрибуту отношения придает его роль в отношении. Атрибут прямой ссылки, представленный интерфейсным Событием, может быть использован: или для открытия назначенной ему объектной Формы целевого класса, или для открытия списковой Формы целевого класса. Во втором случае списковая Форма будет выполнять функции Редактора значения атрибута прямой ссылки, а именно — возвращать в качестве присваиваемого атрибуту значения дескриптор выбранного объекта. Атрибут обратной ссылки хранит список указателей на объекты целевого класса, поэтому его естественной формой представления является представление Множества (объектов). Соответственно, интерфейсному Событию, представляющему атрибут обратной ссылки, можно назначить только списковую Форму целевого класса.

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

Представление Множества

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

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

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

Представление События

Событием считается детерминированное внешнее воздействие. По своему характеру Событие может быть навигационным, интерфейсным, или функциональным. Событие принадлежит конкретному логическому владельцу — Классу, Атрибуту или Множеству, соответствующую функциональность которого Событие инициирует при его возникновении.

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

Порядок формирования интерфейса

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

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

В случае размещения интерфейсного События потребуется еще один шаг — назначение ему Формы класса. Требуемый перечень Форм нужного типа (объектная, списковая, отчетная, файловая) предоставит всплывающий Менеджер Форм. Именно в нем создаются новые Формы (соответствующего типа), с присвоением им пользовательского наименования.

Корневой частью всего много-оконного интерфейса, и точкой входа в него, является Рабочий стол, существующий на правах главного окна приложения. Фактически, Рабочий стол — это такая же объектная Форма класса, как и прочие, но принадлежащая Супер-классу, который является владельцем атрибутов обратной ссылки ко всем прочим Классам модели приложения. Это позволяет использовать Рабочий стол как точку входа в сценарий приложения, размещая на его канве Представления выбранных атрибутов обратной ссылки или в виде Кнопок-ярлыков (в стиле а-ля Windows), или в виде Перечислений.

Декларация пустого Рабочего стола создается автоматически при инициализации системы. Все прочие Формы классов создаются по мере надобности в Менеджере Форм.

 

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

Абстрактное представление

Кортеж Абстрактного представления включает в себя набор свойств, характеризующих отдельное Представление в целом: Type Entity, Type Face, Name и Image, а также кортежи Data- и Face- частей Представления, в каждом из которых декларированы всего два свойства: IDD и Child's List.

Компоненты Data и Face олицетворяют собой двуединую суть Представления. С одной стороны, различные форматы данных, с другой — различные способы реализации их отображения. Обе части пространственно разделены, но должны быть логически связаны. Data унифицирует взаимодействие с мета-сущностями данных и их экземплярами, то же самое делает Face в отношении компонент визуальной библиотеки. Унификация внешнего взаимодействия и внутренняя логическая связь Data и Face на основе общего для них уникального идентификатора экземпляра IDD, необходимы для организации взаимного обмена между данными и их отображением.

Частные Представления

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

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

Кортеж Face-компонента также дополняется элементами — идентификатором используемого компонента библиотеки визуализации и полным набором свойств этого компонента. Свойствам, за исключением координат, присваиваются значения по умолчанию. Отображаемая часть Представления может быть образована более чем одним визуальным компонентом, поэтому дочерние компоненты, также описываемые дополнительными экземплярами кортежа Face, добавляются в кортеж Child's List родительского Face.

Важным свойством кортежа является его логическая совместимость с форматом XML, что позволяет хранить декларации всех частных Представлений в обычном текстовом файле, а при необходимости — там же и редактировать. 

Экземпляр представления

Новый экземпляр представления создается конструкторскими методами, принадлежащими Диалогу размещения, и представляет собой две копии сериализованных кортежей Data- и Face-компонент выбранного Представления. Эти копии добавляются в соответствующие кортежи Child's List экземпляров Data и Face исходного логического владельца, и получают одинаковое значение дескриптора IDD экземпляра, уникальное в пределах данной Формы.

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

Хранение Форм

Формы класса, как и декларации Классов, хранятся непосредственно в объектной базе данных в виде объектов, и для этих целей в объектной модели предусмотрен еще один служебный класс — класс Forms.

Когда в Менеджере Форм создается новая форма, то при этом создается производный объект класса Forms. В своем кортеже, в качестве значений соответствующих атрибутов, объект Forms хранит интерфейсный тип формы (Type Face), дескриптор класса-владельца (IDC), пользовательское наименование Формы (Name), а также сериализованные Data- и Face- структуры.

Дескриптор Формы

Как уже упоминалось, каждый Класс обладает множественным отношением с Супер-классом, образуя в кортеже Супер-класса атрибут обратной ссылки. При этом значение дескриптора IDA этого атрибута в кортеже Супер-класса совпадает со значением системного дескриптора IDC самого Класса. Это совпадение используется, помимо прочего, для организации доступа к Формам классов.

Еще один служебный классOwner Forms, созданный как прямой наследник Супер-класса, логически наследует его полный кортеж. Объект, производный от Owner Forms, является системным объектом с фиксированным дескриптором, который априори известен системе управления данными. Каждое значение в кортеже объекта Owner Forms существует в перечислимой форме, образуя кортеж значений. Когда создается новая Форма класса, и как следствие — объект класса Forms, к кортежу, на который указывает дескриптор Класса в объекте Owner Forms, добавляется новый элемент, в котором сохраняется указатель на вновь созданный объект класса Forms. Таким образом, для доступа к определенной Форме класса достаточно знать ее порядковый номер IDR в Классе — дескриптор Формы

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

Дескриптор IDR Data-компонент Кнопки получает при назначении Формы класса интерфейсному Событию. Когда пользователь нажимает на Кнопку открытия формы, Face-компонент транслирует это событие Data-компоненту, а тот в свою очередь инициирует процесс открытия новой формы, передавая Диспетчеру форм хранимый дескриптор IDR.

Диспетчер форм

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

Диспетчер форм является основным действующим лицом процесса исполнения сценария приложения. Он управляет открытием/закрытием форм класса, а также всеми процессами взаимного обмена данными и событиями пространственно разнесенных частей и компонент формы как между собой, так и с окружающей средой. Для этих целей Диспетчер использует внутреннюю таблицу управления Формами. Порядковый номер записи этой таблицы используется как динамический дескриптор IDW открытой на сервере и у клиента Формы. Так как Data- и Face- взаимодействуют только в пределах своей Формы, дескриптор IDW является обязательным параметром при любом обмене между Диспетчерами на сервере и у клиента.

Получив запрос с дескриптором IDR на открытие формы Диспетчер на сервере: а) получает номер свободной записи в своей таблице, который и станет IDW формы); б) конвертирует полученный с запросом дескриптор Формы класса IDR в IDO объекта Forms; в) копирует значение атрибута Data объекта Forms в память, а указатель на копию сохраняет в таблице; г) извлекает из объекта Forms значение атрибута Face, и вместе с IDW формы отправляет Face-компонент в адрес Диспетчера на клиенте.

Получив IDW и Face, клиентский Диспетчер сохраняет указатель на Face в таблице управления, после чего в соответствии с декларациями Face создает и инициализирует экземпляры компонент библиотеки визуализации. А тем временем Диспетчер на сервере для вновь открытой формы инициирует процесс формирования первой Выборки данных.

Выборка данных

Передача данных от Data- на сервере к Face-компонентам клиента осуществляется оптом, в пакетном режиме, и этот пакет, как и собственно сам процесс его формирования, именуется Выборкой.

Визуально, Форма класса может быть устроена сколь угодно сложно, и содержать множество как простых элементов, так и таблиц, графиков, диаграмм Гантта, .. в самых различных комбинациях. Но есть одна важная особенность: сторона Face- только отображает предоставленные ей наборы данных, а вся логическая зависимость данных реализуется на стороне Data-компонент. То есть, когда на Форме отображается сложная древовидная структура, порожденная каскадной выборкой из связанных таблиц (например: Заказчики Стройки ← Работы ← Ресурсы), то в реальности соответствующий Face-компонент отображает простое линейное перечисление, в каждой записи которого определенные элементы задают горизонтальное смещение и внешний вид отступа. Иное дело, когда на Форме Контрагента раздельно отображаются два перечня: Магазинов и Товаров, например. C точки зрения Face — это два независимых перечисления.

Такая условная индифферентность Face в отношении логической связанности данных позволяет упростить и унифицировать внутреннюю организацию Выборки, которая принимает вид кортежа значений, в котором любое Множество (таблица, диаграмма) занимает всего один элемент, значением которого является кортеж, элементами которого являются кортежи записей перечисления, каждым значением которого в свою очередь является кортеж значений дочерних Множеству прочих Представлений. Таким образом, для получения доступа к отдельному значению Выборки как со стороны Data-, так и стороны Face-, достаточно составного идентификатора в терминах: [порядковый номер Множества (если вне Множества, то 0) на форме] + [порядковый номер относительно владельца, Формы или Множества]. Именно в таком понимании и реализован упомянутый ранее IDD экземпляра, который синхронно присваивается компонентам Data и Face при создании экземпляра Представления

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

Сформированная таким образом Выборка отправляется Диспетчеру на клиенте, который по факту ее получения инициирует процесс отображения. А именно, последовательно обходит структуру Face-компонент, инициируя каждый из них на отображение.

События пользователя

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

Получив IDD [+ значение], Диспетчер на сервере просмотром Data-структуры локализует целевой экземпляр, и инициирует его исполнительную функцию, как правило, единственную. После чего формирует новую Выборку данных, отправляет ее клиенту, и ждет его реакции.

Кроме генерации изображения и диспетчеризации ввода пользователя на стороне клиента больше ничего интересного не происходит. Это и не удивительно, так как все используемые Face-компоненты, с точки зрения их функциональной нагрузки, что называется — на одно лицо. Иное дело, компоненты Data-, которые в зависимости от типа ассоциированной сущности и ее формы представления обладают различным набором добавленных свойств и реализуют различное поведение.

Вариативность Data- стоит рассмотреть на конкретных примерах, самый простой из которых — это объектная форма Класса.

Объектная форма

Объектная форма инициируется Событиями Класса: интерфейсным ”Открыть [объект]”, или функциональным ”Создать [объект]”, и предоставляет доступ к содержимому конкретного объекта Класса. Соответственно, корневой Data-CO [Class Object] формы должен владеть дескриптором класса и указателем на текущий объект этого класса, который в дальнейшем предоставляется дочерним Data-V_ и Data-R_ (значащих и ссылочных Атрибутов) и Data-A_ (Событий) для извлечения/сохранения значений и прочих действий с объектом. Указатель на объект класса Data-CO может получить или принудительно извне, от Data-AO [Action Open] Кнопки ”Открыть”, или как результат исполнения собственного запроса к сервису объектов (к Data API) на создание нового объекта класса.

Типичным представителем объектной формы является Рабочий стол приложения.

Рабочий стол

У Рабочего стола есть своя специфика: а) это Форма, создаваемая автоматически; б) это Форма представления Супер-класса; в) дескрипторы Супер-класса и производного от него Супер-объекта присваиваются Data-CO Рабочего стола автоматически, так как они априори известны.

Как уже упоминалось, кортеж Супер-класса образован исключительно атрибутами обратной ссылки всех прочих классов. Выбранный атрибут можно разместить на канве Рабочего стола одним из двух способов: или в виде Перечисления объектов целевого Класса, или в виде интерфейсной Кнопки.

Реализацию первого способа рассмотрим чуть ниже, на другом примере, а по второму способу дочерним компонентом Data-CO Рабочего стола становится Data-RB [Reference Backward]. Этот компонент владеет дескрипторами ссылочного Атрибута и назначенной ему списковой Формы целевого Класса. При отработке пользовательского события, Data-RB сначала инициирует открытие Формы, после чего обращается к Атрибуту за значением — указателем на список обратных ссылок, и присваивает этот указатель корневому Data- открытой им Формы. 

Списковая форма

Корневой Data-CL [Class List] списковой Формы структурно и функционально устроен несколько сложнее, чем Data-CO объектной. В добавление к дескриптору Класса и указателю на объект, Data-CL также владеет полученным извне указателем на список, указателем на позицию текущего элемента в списке (читай, текущим объектом), и дополнительным Data-компонентом — Data-CE [Class Enum], который управляет собственно перечислением объектов Класса в окне видимости.

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

Так как несложно соотнести визуальные размеры одного элемента и окна видимости, то максимальное количество отображаемых окном элементов известно заранее. Это количество и будет считаться далее размером окна. Соответственно, Data-CE владеет размером окна, позицией первого элемента окна в исходном списке (позицией окна) и позицией текущего элемента в окне. Этого набора данных достаточно, чтобы последовательно, по одному, запрашивать у Data-CL указатели на объекты данных из списка, и для каждого из них инициировать образующие колонки дочерние Data- из Child's List, тем самым формируя табличную часть выборки.

С целевым списком Data-CL взаимодействует не напрямую, а через набор функций, которые предоставляет ему функционал списка, отвечающий за формирование всех списков в системе.

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

Три горячих клавиши: Enter, Inset и Delete перманентно зарезервированы для "открытия", создания и удаления объекта данных. Для каждой из клавиш в Child's List компонента Data-CL автоматически создаются три дочерних компонента: Data-AO, Data-AC и Data-AD [Action Delete] соответственно. Первым двум компонентам можно назначить объектную Форму, которая будет открыта при соответствующем Событии, а также всем трем — запретить реакцию на ассоциированное с компонентом Событие.

Обратим внимание. Представления Атрибутов и Событий исходного класса могут быть размещены и за пределами канвы Перечисления — непосредственно на канве списковой Формы. В этом случае их Data-компоненты будут зарегистрированы в Child's List компонента Data-CL, который предоставляет своим дочерним компонентам в качестве источника значений текущий объект списка. 

Дочернее множество

Так в частности, выделив для этого соответствующее свободное место, на Форме с перечнем Поставщиков можно разместить список Товаров, отображающий их наличие у текущего Поставщика. В данном случае мы размещаем атрибут обратной ссылки на класс Товары [Поставщика] не виде Кнопки с назначенной ей списковой Формой, как на Рабочем столе, а непосредственно в виде Перечисления.

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

Ремарка: Для удобства изложения все происходящее описывается как поведение того или иного действующего лица, хотя по факту это далеко не так. Реальным и единственным исполнителем на сервере является функционал Диспетчера, который использует декларации совокупности Data-компонент Формы сначала для отработки события пользователя, а затем для формирования новой Выборки.

Обратим внимание — роль ведущего может выполнять как Data-CL, так и Data-CO, в равной степени владеющие текущим объектом. Иными словами, визуальное Перечисление Товаров (в виде дочернего Множества) можно аналогичным образом разместить и на канве объектной Формы класса Поставщик. При этом будет логичным предположить, что если некоторое Перечисление является представлением атрибута обратной ссылки текущего объекта, то создание нового объекта в этом Перечислении должно сопровождаться автоматическим присвоением дескриптора текущего объекта атрибуту прямой ссылки нового объекта. Модель данных позволяет реализовать такое "создание в контексте" тривиально просто и естественно: вызовом метода Create не в адрес целевого класса, а в адрес атрибута обратной ссылки, с указанием дескриптора исходного (текущего) объекта.

Диалог выбора объекта

Прямая ссылка может быть реализована пользовательским выбором конкретного объекта из их множества в интерфейсном диалоге, который так и называется — Диалог выбора. Для исполнения такого Диалога, который по факту будет выполнять функцию Редактора ссылочного значения, подходит любая списковая Форма целевого класса. Результатом же исполнения будет присвоение атрибуту прямой ссылки дескриптора объекта, выбранного пользователем в Диалоге.

Для организации Диалога выбора атрибут прямой ссылки размешают в виде Кнопки, инициирующей диалог, и назначают ей списковую Форму целевого класса. В результате такого размещения в Child's List владельца — Data-CO, Data-CL или Data-CE, будет добавлен компонент Data-RD [– Reference Direct], который хранит дескриптор IDA атрибута [прямой ссылки], а также дескриптор IDR назначенной Формы.

При получении события в свой адрес, Data-RD инициирует открытие назначенной ему Формы, сохранит в своих свойствах ее IDW, а корневому Data-CL вновь открытой Формы передаст на хранение собственный идентификатор (IDW+IDD). Наличие у Data-CL актуального идентификатора Data-RD изменяет стандартное поведение списковой Формы — теперь пользовательский Enter или Double Click воспринимаются не как попытка "открыть" выбранный в списке объект, или начать редакцию его атрибутов, а как событие выбора. По этому событию Data-CL передает инициировавшему диалог Data-RD дескриптор IDO своего текущего объекта и инициирует принудительное закрытие собственной Формы (Диалог выбора). В свою очередь Data-RD присвоит ссылочному атрибуту (IDA) текущего объекта своего владельца полученный IDO как значение. И это очень важный момент, на который стоит обратить пристальное внимание: все операции с дескрипторами объектов могут быть выполнены исключительно или от имени объектной модели данных, или от имени Data-компонент на сервере, и никак не доступны для иных внешних манипуляций.

Также обратим внимание, Data-CL формы диалога не получал конкретного указания на место, откуда следует взять список, и поэтому по умолчанию воспользовался дескриптором своего класса для получения IDO списка у Супер-объекта — то есть полное множество объектов класса. Между тем, в качестве исходного мог быть использован и список обратных ссылок из конкретного объекта данных.

Каскадный выбор

Не все Диалоги выбора имеют столь простую организацию. Предположим, что в исходной модели приложения существует класс Категория, и каждый Поставщик относится к конкретной товарной Категории. Тогда, связывая Товар с конкретным Поставщиком было бы удобно выбирать сначала соответствующую Категорию, и затем уже — Поставщика в этой категории. Для этого, пользуясь тем обстоятельством, что ссылочный атрибут открывает доступ к кортежу целевого класса, в диалоге управления свойствами Data-RD (класс Поставщик) выберем в качестве дополнительного условия атрибут прямой ссылки на класс Категория, и назначим ему соответствующую списковую Форму. Тогда в Child's List уже существующего Data-RD добавится еще один Data-RD, который будет управлять выбором Категории.

Теперь, исходный Data-RD сначала инициирует Форму своего дочернего Data-RD, и после успешного выбора на этой Форме объекта Категория, и не закрывая ее, инициирует Форму Поставщики, которой в качестве условия, определяющего источник списка, передаст IDO выбранного ранее объекта класса Категория и IDA атрибута обратной ссылки в этом объекте. Обе формы диалога будут закрыты одновременно по инициативе исходного Data-RD при получении им конечного результата выбора. 

Дизайн и исполнение

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

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

При размещении на Форме нового Представления, его Data- часть одинаковым образом будет добавлена в исполняемую и кэшируемую Data-, а Face-часть — только в кэшируемую, после чего будет отправлена клиенту. Собственно говоря, именно факт поступления от сервера Face-части нового Представления фактически закрывает Диалог размещения на клиенте, с добавлением этой части в действующую структуру Face- на клиенте.

Изменение свойств выбранного визуального компонента осуществляется средствами Редактора координат, а также инициируемых им Диалогов управления свойствами. При этом все сделанные дизайнером изменения вносятся в непосредственно в копию Face- на клиенте, с их периодическим промежуточным сохранением (отсылка полной копии Face- на сервер). Окончательное сохранение Face-, как уже упоминалось, произойдет при закрытии Формы.

Нечасто, но на практике неизбежно случается так, что сделанное изменение хочется аннулировать. Механизм отката реализуется достаточно тривиально: хранением на сервере нескольких синхронных копий Date- и Face-. Откат возможен строго до тех пор, пока Форма не закрыта.

Copy & Paste

Хранение данных в формате кортежа позволяет однозначно конвертировать содержимое структур Data- и Face- в формат XML, и обратно. Что в свою очередь позволяет реализовать или экспорт/импорт содержимого Формы целиком, или копирование/вставку выделенного множества логически связанных компонент Формы через буфер обмена, и тем самым резко повысить скорость создания приложения.

Из практического опыта — импорт/экспорт используется гораздо чаще, чем можно было бы подумать. Импорт содержимого подходящей Формы, даже с учетом того, что в Data- придется переназначить классы и атрибуты, осуществляется гораздо быстрее, чем последовательное формирование содержимого новой Формы путем выкладки представлений с последующей подгонкой координат.

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

Полиморфизм

Класс-наследник получает в свое владение не только полный кортеж атрибутов Класса-предка (полиморфизм свойств и поведения), но также и полный набор его Форм, любую из которых он может переопределить для собственных нужд, реализуя тем самым полиморфизм представления.

Как мы помним, создаваемый наследованием Класс получает копию пустого кортежа предка, не содержащую деклараций Атрибутов класса. При обращении к Атрибуту, не имеющему декларации, выполняется пере-адресация на кортеж предка, которая будет повторятся до тех пор, пока искомая декларация не будет получена. Собственный экземпляр декларации Атрибута наследник получит только тогда, когда внесет в нее свои частные изменения. Абсолютно аналогичный механизм действует и при наследовании Форм, с той лишь разницей, что для адресации используются кортежи из Owner Forms.

В качестве примера. В некотором приложении все многообразие Документов создано наследованием от класса-предка "Документ", который определяет общий для всех набор реквизитов: "Дата", "Номер", "Название", etc. Естественно, что каждый наследник не только расширяет набор частных реквизитов, но и имеет свой сугубо специфичный внешний вид. При этом на Форме "Документооборот" все Документы присутствуют в общем списке, но каждый "открывается" в своей уникальной Форме, которая получена модификацией унаследованной Объектной формы общего предка, и обладает одинаковым с ним дескриптором IDR. И хотя загружающий и инициализирующий Форму Data-AO и принадлежит типизированному классом-предком Data-CL, но этот Data-AO в качестве указателя на целевой Класс использует значение типа (он же — класс), взятое из текущего объекта Data-CL.

Полиморфизм Форм представления является существенным дополнением полиформизма Классов — способности отдельного Класса обладать множеством форм своего интерфейсного представления.

Вариативность

Для разных пользователей одна и та же Форма класса может выглядеть по разному. По разным причинам, начиная от требований, предъявляемых к приложению, и заканчивая тривиальной зависимостью внешнего вида и содержимого Формы от размера экрана оконечного устройства конкретного пользователя.

Реализовать пользовательскую вариативность совсем несложно, если воспользоваться аналогией с уже имеющимся механизмом создания множества локальных баз данных в пределах одной физической. Вспомним, единственный объект Owner Forms с априори известным дескриптором хранит полный набор Форм каждого Класса. Этот набор форм будем считать базовым (фоновым), а для хранения указателей на альтернативные варианты Формы воспользуемся дополнительными объектами Owner Forms. Для управления этими объектами нам понадобится еще один служебный класс — Config. Этот класс обладает атрибутом "Name", и связан отношениями: "один-ко-многим" с классом Users и "один-к-одному" с классом Owner Forms. Наличие отношения "один-к-одному" позволяет при создании новой именованной "конфигурации" автоматически создавать ассоциированный с ней объект Owner Forms.

Если "Пользователю назначена Конфигурация" (в объекте Users актуален указатель на объект Config), то загрузка Формы начинается с попытки извлечь указатель на нее из альтернативного объекта Owner Forms. И если эта попытка неудачна, то будет использована Форма из базовой конфигурации.

Указатель на альтернативный вариант Формы попадает в объект Owner Forms выбранной конфигурации автоматически, в результате модификации Формы пользователем-Дизайнером, который предварительно выбрал для работы данную конфигурацию. При этом базовый вариант Формы остается неизменным, а для хранения модифицированного контента создается новый объект Forms.

Права доступа

Права отдельных групп пользователей можно ограничить, или частично — запретив им редактировать данные на указанных Формах, или полностью — запретив эти Формы открывать.

Механизм реализации запрета полностью идентичен механизму реализации вариативности, с той лишь разницей, что вместо служебного класса Config используется еще один служебный класс — Access. Каждый объект класса Access обозначает именованную группу пользователей, обладающих общим набором прав (ограничений), а связанный с Access объект Owner Forms хранит не указатели на объекты Forms, а идентификаторы типа ограничения для этих Форм, если таковые заданы.

Диалоги дизайна

Для управления дизайном Формы необходимы соответствующие инструменты: Диалог размещения, Диалог выбора формы, Диалоги управления частными свойствами Data- и Face- компонент, и множество еще более мелких и никак не упомянутых Диалогов.

Реализация любого диалога управления в архитектуре клиент-сервер является достаточно трудоемкой задачей. Но у нас есть возможность эту задачу существенно упростить, если для ее решения использовать уже рассмотренные выше методы. В самом деле, абсолютно ничто не мешает реализовать все инструментальные Диалоги по образцу Форм пользовательского сценария, вдобавок пропустив их через все те же Диспетчеры на сервере и клиенте. Более того, точно так же реализуется все множество Диалогов управления моделью приложения, в совокупности образующих Конструктор этой модели.

За скобками

Разумные ограничения на объем статьи не позволяют включить в нее рассмотрение специфики и примеров отчетных (печатных) и файловых интерфейсов, принципы реализации которых не сильно отличаются от выше изложенных.

Резюме

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

 

 

 


<<< назад к оглавлению