H M
Внедрения Демо Мнения Oracle Клиентам Контакты Работа
Универсальный интерфейс: полёт нормальный

Автор: Абдрахимов Рустам Маратович
Ведущий эксперт отделения телекоммуникационных систем компании «Форс-центр разработки»

  • Статья впервые опубликована в журнале Oracle Magazine Russian Edition. Август 2008 г.

Несколько лет назад нас - группу разработчиков АСР «Fastcom» постигло глубокое разочарование: корпорация Oracle отказалась от эволюционного развития Forms. Наша же биллинговая система, зародившаяся ещё в прошлом веке, целиком и полностью разрабатывалась на Oracle Designer + Oracle Developer - технологиях, казавшихся вечными: Стоимость перехода на другую платформу была соизмерима с годовым бюджетом нашего проекта. К счастью, мы что-то подобное предвидели, и ещё в 2001 году была поставлена задача: разработать принципы универсального интерфейса, который бы не зависел от средств его отображения. На первый взгляд идея кажется бредовой, но прошедшие 7 лет позволяют подвести некоторые итоги работы в данном направлении, которыми я постараюсь поделиться.

Что это такое

В основу универсального интерфейса, разработанного нами, положены следующие принципы:

1.Разработчик прикладной задачи (или по другому - бизнесс-логики) манипулирует только общими для любого средства отображения интерфейса понятиями, не опускаясь до конкретики, свойственной каждому из этих средств, так называемым «движкам»;

2.Разработчик прикладной задачи никогда заранее не знает, кто и как будет интерпретировать заданные им свойства элементов интерфейса;

3.Разработчик прикладной задачи, создавая интерфейс к ней, не использует процедурные языки, не оперирует обработчиками событий, зато он может использовать механизм «Динамических свойств»;

4.Разработчик конкретного «движка» для отображения элементов интерфейса использует все возможности данного ему инструмента для адекватной интерпретации свойств объектов, указанных разработчиком прикладной задачи.

Теперь постараюсь подробнее прокомментировать вышеизложенные принципы.

Что такое «движок»? Это средство (среда + язык) отображения интерфейсных элементов, с помощью которых пользователь просматривает, ищет и модифицирует данные бизнесс-логики. Самый первый наш «движок» был написан на Oracle Forms, хотя это самое неподходящее средство для данной задачи. «Движок» может быть реализован на C++, Delphi, Java, JavaScript и даже в виде плагина к FARу.

Что представляют собой «понятия», которыми манипулирует разработчик прикладной задачи, применяющий универсальный интерфейс? Это элементы и их свойства, которые по-разному могут быть реализованы в разных «движках», в зависимости от возможностей конкретного «движка». Например, не стоит заводить такое свойство элемента, как «Цвет шрифта», ведь разные средства отображения могут иметь разные понятия о цвете. Одни могут выдать 24-х битную палитру, а другие работают в монохромном, чёрно-зелёном интерфейсе. Или другой пример. Нельзя явно указывать тип интерфейсного элемента, такой как ListBox, RadioGroup, или ListOfValues. Разные средства могут иметь немыслимые ограничения. Мы должны иметь возможность задать список возможных (или типичных) значений. А дальше уже пусть сам «движок», вернее его автор, выкручивается, чтобы адекватно интерпретировать свойства, заданные разработчиком прикладной задачи.

Вывод из второго пункта такой: разработчик прикладной системы не обязан знать среду и язык средства отображения интерфейса. И это очень хорошо! Отношения между разработчиком «движка» и бизнесс-логики формализованы на уровне описания свойств элементов интерфейса, а далее каждый занимается сугубо своим делом. Разработчик прикладной системы, спец. в предметной области, не тратит время на размещение интерфейсных элементов на экране, а разработчик «движка» может быть совершенно не в курсе конкретной предметной области, для которой, собственно, разрабатывается прикладная система.

Раз разработчик интерфейса не подозревает о том, кто и как будет интерпретировать плоды его труда, то он не может оперировать терминами «обработчиков событий», ведь заранее не известно - какие события могут обрабатываться тем или иным «движком». Но даже если бы не это, разработчику интерфейса всё равно неизбежно пришлось бы писать логику на языке средства отображения интерфейса, что противоречит нашим требованиям. Однако, мир гибок и его элементы взаимосвязаны. Как же реализовать эту взаимосвязь? Вот тут то нам и помогут «динамические свойства», то есть это свойства, значения которых могут задаваться не только константой, но и структурой, описываемой декларативным языком, значение которой может зависеть от значений любых свойств любых интерфейсных элементов. Динамическое свойство перевычисляется в тот момент, когда изменяется хотя бы одно значение свойства, на которое ссылается данное динамическое свойство. К примеру, в нашей реализации в качестве языка динамических свойств используется язык SQL.

Когда это полезно

Теперь, когда изложены основные принципы универсального интерфейса, самое время ответить на вопрос: «А зачем это всё надо?».

Большинство заказных проектов развивается экстенсивным путём: обследование → техническое задание → реализация. Затем новые требования → техническое задание → реализация. Снова «безумные» требования заказчика → техническое задание → реализация. Потом исправление неверных решений заказчика (за их же счёт) с помощью нового ТЗ → реализация. Здесь главное - хорошее техническое задание. Потом группа программистов, используя самый подходящий инструмент, реализует ТЗ вплоть до последней причуды заказчика. Не успеваем? Набираем студентов, 2 недели на обучение, ТЗ в руки - и они работают на благо проекта.

Теперь представим, что заказчиков несколько, и не 2 - 3, а десятки, а может быть и сотни. Возможно, бизнес заказчиков похож, но не настолько, чтобы один раз создать проект, а потом инсталлировать его всем, как Windows. В этих условиях немыслимо держать отдельную группу разработчиков - программистов на каждого заказчика. Неизбежно возникает мысль о тиражном проекте.

Что же такое «Тиражный проект» с точки зрения интерфейса? Многочисленные настройки - глобальные и локальные параметры, справочники, конфигурации могут до неузнаваемости повлиять на вид одной и той же формы у разных заказчиков. Простой пример: включение мультивалютности в системе приведёт как минимум к появлению дополнительного столбца (в национальной валюте) в каждой форме, где есть поле или столбец с ценой или стоимостью.

Чем больше заказчиков, тем формы становятся всё более интеллектуальными. Бизнес-требования одних потребителей не должны шокировать других? И мы увеличиваем количество параметров и расширяем справочники: Заказчиков уже столько, что мы не успеваем заводить новые сущности? Тогда мы выпускаем инструмент для создания и описания бизнес-логики новых сущностей. И так далее: Бедные интерфейсные формы!

Куда двигаться

Формализуем наши требования к интерфейсу:

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

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

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

г).Внедрение нового «движка» должно занимать не более полугода. Он должен уметь отображать все формы, разработанные до его появления. Формы, разработанные после внедрения нового «движка», должны правильно работать на старых средствах отображения интерфейса (двусторонняя совместимость).

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

Как это работает

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

Пример: Элементы типа «Пункт меню» имеют естественную иерархию. При навигации на листовой Пункт меню в некоторой определённой области отображаются элементы типа «Список». Каждый список в подчинении имеет Строки и Действия (такие, например, как «Добавить», «Поиск» и т.п.). Элементы типа «Строка» состоят из Полей и тоже могут иметь Действия (такие как «Изменить», «Удалить»), а при навигации по строкам может отображаться другой Список (или Списки), реализуя концепцию «Мастер - Деталь».

Это только маленький кусочек всей идеи, приведённый для примера. Ведь есть ещё Бланки, Поля ввода данных, Локальные меню со своими Пунктами и, наконец, Группы, объединяющие элементы разных типов по интересам.

Вся иерархия объектов описывается древовидной структурой Протоэлементов. Почему «прото»? Потому что каждый протоэлемент может порождать от 0 до N элементов интерфейса одного типа. Протоэлементу сопоставлен SQL-запрос. Количество строк, возвращаемое этим запросом, определяет число порождаемых интерфейсных элементов. SQL-запрос может опираться на параметры, переданные с вышестоящего уровня, и он же порождает свои параметры или переопределяет значения родительских параметров (если их имена совпадают). Параметры с любого уровня могут использоваться в динамических свойствах интерфейсных элементов.

Для примера, рассмотрим иерархию Протоэлементов, определяющую древовидную структуру объектов словаря Oracle:

Select USERNAME, UserName PROMPT from All_Users order by 1
 Select Max('Таблицы') PROMPT from All_Tables where owner='{USERNAME}'
  Select TABLE_NAME, Table_Name PROMPT from All_Tables where owner='{USERNAME}' order by 1
   Select 'Столбцы' PROMPT from dual
    Select COLUMN_NAME, Column_Name PROMPT, DATA_TYPE from All_Tab_Columns
           where owner='{USERNAME}' and Table_Name='{TABLE_NAME}' order by Column_ID
   Select 'Первичный ключ' PROMPT from dual
    Select CONSTRAINT_NAME, Constraint_Name PROMPT from All_Constraints
           where Owner='{USERNAME}' and Table_Name='{TABLE_NAME}' and Constraint_Type='P'
     Select Column_Name PROMPT from All_Cons_Columns where Owner='{USERNAME}'
           and Constraint_Name = '{CONSTRAINT_NAME}' order by Position
   ...
   Select 'Внешние ключи' PROMPT from dual
    Select C1.CONSTRAINT_NAME, C1.Constraint_Name||' ('||C2.TABLE_NAME||')' PROMPT
           from All_Constraints C1, All_Constraints C2 where C1.Owner='{USERNAME}'
           and C1.Table_Name='{TABLE_NAME}' and C1.Constraint_Type='R'
           and C1.R_CONSTRAINT_NAME=C2.CONSTRAINT_NAME and C1.R_OWNER=C2.OWNER
     Select Column_Name PROMPT from All_Cons_Columns where Owner='{USERNAME}'
            and Constraint_Name = '{CONSTRAINT_NAME}' order by Position
 ...
 Select Max('Пакеты') PROMPT from All_Objects where owner='{USERNAME}' and Object_Type='PACKAGE'
  Select Object_Name PACKAGE, Object_Name PROMPT from All_Objects
         where owner='{USERNAME}' and Object_Type='PACKAGE' order by 1
   ...
...

Если параметр PROMPT использовать в качестве имени узла иерархии, то получится структура, изображённая на иллюстрации.

Динамически определяемая иерархическая структураНа этом наглядном примере, понятном каждому ораклисту, проиллюстрирован процесс описания объектов интерфейса, определяющий их суть, количество и отношения друг с другом. Однако этого недостаточно. Надо ещё уметь определять качественные характеристики интерфейсных объектов, такие как: роли, выделение, группирование, поведение, способы взаимодействия с миром и друг с другом и т.п. Всё это определяется свойствами, которые свои для каждого типа объектов интерфейса. Именно их интерпретирует каждый «движок» в меру своих возможностей.

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

В простейшем варианте, свойство целиком может состоять из динамического элемента. Такой случай мы уже встречали - значение свойства «Название пункта меню» целиком состоит из ссылки на контекстный параметр PROMPT.

Подумаем - какое ещё свойство, кроме названия, может быть у пункта меню. Первое что приходит в голову - имя иконки, которой может помечаться каждый пункт меню. Однако прямо так называть свойство не стоит, так как наш интерфейс, возможно, будут интерпретировать «движки», которые не смогут отображать иконки. Иконки, по сути, являются средством ролевой раскраски пунктов меню. Вот именно так и стоит называть это свойство: «Роль пункта меню». Далее, в продвинутых «движках», каждой роли может быть сопоставлена иллюстрирующая её иконка.

Ещё одно полезное свойство - группирование пунктов внутри одного уровня. В нашем примере мы могли бы не делать отдельные ветки меню для разных типов ключей таблицы, а отобразить их на одном уровне, отсортировать и сгруппировав по типам: первичный ключ, уникальные ключи, внешние ключи. Опять же, разные «движки» могут по-разному интерпретировать свойство «Группа». Одни отделять группы пунктов друг от друга полуторным интервалом, другие - выделять разные группы цветом. Но в любом случае, это - не забота разработчика бизнес-логики. Его дело - сгруппировать элементы. А как группировка будет реализована - даже думать ему не стоит.

Может возникнуть естественный вопрос: а где же такие свойства пунктов меню, как «Уровень вложенности», «Раскрытый»/«Закрытый», «Листовой»/«Промежуточный», «Активный»/«Пассивный» и т.п.? Некоторые из этих свойств являются вторичными, а другие - технологическими для каждого конкретного «движка». Именно то, что разработчик прикладной системы не вдаётся в эти тонкости, является одним из главных преимуществ универсального интерфейса.

Кому это надо

Замечу, что мне всё время приходится преодолевать недоумение новых членов нашей команды, которые впервые сталкиваются с универсальным интерфейсом. Внимательно выслушав лекцию, очень похожую на всё вышеизложенное, они, немного подумав, говорят до смешного очень похожие слова: «Зачем так сложно? Почему нельзя взять и быстро написать нужные формы, используя такой замечательный инструмент, как JDeveloper / NetBeans / Eclipse / APEX / BEA WorkShop: (нужное подчеркнуть)».

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

В чём опасность

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

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

Что дальше

Итак, допустим, у вас уже реализован «универсальный интерфейс». Что ещё можно оптимизировать? Дам несколько советов.

1.Можно создавать типовые Списки и Бланки, иными словами «универсальные формы универсального интерфейса». Это ещё более высокий уровень абстракции. Такие интерфейсные модули будут типовым образом обслуживать типовые ситуации. Пример: Список и Бланки для отображения и редактирования справочной таблицы с Кодом, Наименованием и Примечанием. Единственным входным параметром такого Списка будет имя таблицы, содержащей справочник.

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

  • CODE: Столбец, содержащий код записи, который является первичным ключом. Как привило, используется в справочниках. Типовые свойства: обязателен к заполнению, не длиннее N символов, не может быть изменён, преобразуется к верхнему регистру, содержит только буквы латинского алфавита, цифры и знак подчёркивания.
  • NAME: наименование чего-то. Типовые свойства: Текстовая строка, обязательна к заполнению, не длиннее M символов, может иметь многоязычные вариации.
  • HIST_FROM: Дата/время, обязательна к заполнению, может вызывать типовой календарь для редактирования значения.
  • UID: Уникальный идентификатор. Не может редактироваться. При вставке заполняется автоматически функцией Sys_Guid()

Ну и так далее. Таких «типовых» полей наберётся десятка три. Это значит, что при создании интерфейсных элементов при указании имени столбца автоматически можно устанавливать некоторые свойства в предопределённые значения, которые, при необходимости, могут быть изменены в каждом индивидуальном случае. Очень удобно.

3.Такие же роли можно придавать и интерфейсным элементам. Если код действия указывается равным «OK», то оно имеет свойство действия по умолчанию, назначается надпись (подпись) «Сохранить» и комментарий «Запомнить изменения и выйти», имеет характерную пиктограмму. Если код строки указывается равным TOTAL, то они добавляется в конец (на данном уровне), выделяется ролью, снабжается действием «Перевычислить» ну и т.д. Опять же, любое свойство в любой момент можно изменить, но обычно, при грамотной предварительной настройке, разработчика всё устраивает.

Удачи!

© ФОРС – Центр разработки. Все права защищены