Мотивация
Определение и комментарии
Подготовка ресурсов
Хранение и эксплуатация
Ресурсы как данные
История эксперимента
Условия эксперимента
Ресурс-объекты, и как они используются
Особенности работы с ресурсами
Профессия: коифигуратор

В первой статье, опубликованной в предыдущем номере журнала, мы рассматривали механизм ресурсов в системе Х Window. Здесь мы обсудим идею ресурсов в ее общем виде, с точки зрения обьектно-ориентированного программирования. А затем, в качестве второго примера, опишем небольшую систему ресурсов, предложенную автором и его друзьями.

Мотивация

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

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

Объект имеет право создать один или много подчиненных объектов. Но в этом случае в список параметров конструктора объекта-начальника должны входить параметры и объектов-подчиненных. Это крайне неудобно. Например, при формировании пользовательского интерфейса у окна обычно задается подокно заголовка, который, в свою очередь, имеет атрибут "цвет фона". При создании окна этот атрибут надо будет указывать явно. А если у заголовка 30 разных атрибутов-параметров? А у окна есть еще подокно рамки и пара полос прокрутки (scroll-bars)? А если в окне имеется еще и нечто содержательное?

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

Итак, мы не нашли модуля в программе, в котором было бы уместно задать внутренние параметры настройки для объектов. Но тогда давайте этот модуль напишем и назовем его системой ресурсов.

Определение и комментарии

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

Как правило, механизм ресурсов подразумевает реализацию специального языка описания ресурсов или задание ресурсов в интерактивном режиме. Язык описания ресурсов состоит из директив, каждая из которых определяет один ресурс. (Интерактивные варианты далее не обсуждаются).

Таким образом, настройка конкретных данных объектов выносится из модулей на алгоритмическом языке и попадает в специальное хранение ресурсов, действующее по своим собственным правилам. Если такой механизм работает, мы сразу получаем несколько достоинств:

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

- при создании нового класса программист может не бояться использовать произвольно большое количество параметров настройки и тем самым добиваться максимальной гибкости для объектов этого класса;

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

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

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

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

Отметим, что если программист не обязан задавать внутренние параметры (они же - ресурсы) при создании своих текстов, он не обязан их задавать вообще: вполне актуально при создании программы выделить из команды специального человека (мы назывем его конфигуратором), который будет заниматься всеми ресурсами всех объектов одновременно. О важности работы конфигуратора мы поговорим позже.

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

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

Подготовка ресурсов

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

Семантика языка должна определять механизм вычисления приоритетной директивы: при запросе ресурса с определенным именем для определенного объекта система ресурсов должна проанализировать все приемлемые директивы и выбрать самую подходящую. Это похоже на главную задачу юриспруденции: какой из множества законов более актуален при разборе конкретного юридического казуса?

Напомним, что в Х Window подбирается наиболее "специфическая" директива. Нам кажется более приемлемым другой вариант: в файле описания ресурсов директивы идут в некотором порядке, так почему бы не сделать этот порядок главным? Другими словами, правильной директивой можно считать последнюю из всех подходящих. Это проще и для работы с языком ресурсов (одно простое правило вместо четырех сложно запоминаемых), и для программной реализации. (Если, как в Х Window, настройка ресурсов содержится в нескольких файлах, для определения последней директивы надо будет зафиксировать еще и порядок просмотра файлов.)

Хранение и эксплуатация

В различных системах ресурсов по-разному может решаться следующая проблема - когда обрабатывать описание ресурсов: (1) на этапе подготовки программы (компиляция+редактирование связей), (2) на этапе ее выполнения, (3) между этими этапами.

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

Второй способ - способ Х Window. Безусловно, он самый универсальный, поскольку он предельно облегчает доступ конечного пользователя к редактированию ресурсов. Однако и этот способ имеет недостатки: он "слишком" открыт, и неквалифицированный пользователь может своими действиями серьезно повредить систему; кроме того, в этом случае максимальны требования к возможностям компьютера. Если недостатки способа перевешивают достоинства - несложно перейти к третьему способу. Только он позволяет редактировать структуру ресурсов в сеансе работы: добавить или уничтожить тот или иной ресурс.(Однако автору это свойство не кажется полезным. Куда более эффективен механизм контекстов, описанный ниже.)

Из сказанного следует, что имеют смысл различные решения следующих двух проблем: предоставляется ли возможность изменять в сеансе работы программы структуру ресурсов и (или) их значения? В Х Window приняты ответы "Да, Нет". Ниже мы покажем, что ответы "Нет, Да" приводят к интересным следствиям.

Ресурсы как данные

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

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

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

Интенсивно развивающиеся объектно-ориентированные базы данных решают задачу хранения уже существующих объектов, в отличие от нашей задачи - конфигурации создающихся объектов.

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

История эксперимента

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

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

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

Условия эксперимента

Описываемая система ресурсов используется так:

1) оформляется файл описания ресурсов на специальном языке;

2) специальный компилятор изготавливает из текстового файла ресурсов двоичный файл;

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

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

Ресурс-объекты, и как они используются

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

Паспорт ресурс-объекта

Ресурсы в системе могут быть подобраны по любому ресурс-объекту, содержащему следующие поля:

class rsrObject { public: // адрес объекта-предка
char* NameClass; // имя класса объектов
char* NameObject; // имя объекта
char* EnvKey; // ключ окружения } ;

(В терминах языка С++ ресурс-объект - это объект любого класса, производного от класса rsrObject.) Поля заполняются в программе при создании объекта и служат паспортом при подборе для него ресурсов. Поясним их смысл.

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

Имя класса - цепочка символов - подразумевается одинаковым для всех объектов одного класса.

Имя объекта - цепочка символов - помогает отличать данный объект от других объектов этого же класса.

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

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

Типы ресурсов

В отличие от Х Window, где имеется около 40 базовых типов ресурсов, а также возможность создания дополнительных типов, в описываемой системе зафиксировано только одиннадцать типов ресурсов. Дело в том, что в Х Window типы ресурсов учитывают различия их использования. У нас - лишь варианты внутреннего хранения. Список наших типов состоит из четырех числовых типов, цепочки символов, четырех типов числовых массивов, массива цепочек и типа "прямоугольник" (сводящегосяпоследовательности четырех целых чисел).

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

Для каждого типа ресурсов в программе предусмотрен специальный вызов, подбирающий ресурсы данного типа. Например, следующий вызов возвращает указатель на ресурс "String" типа "цепочка символов" для объекта, на который указывает Obj.

rsrObject* Obj;
char* result resourceManager::
TextGet(Obj, "String");

Имена ресурсов - цепочки символов. Как и поля ресурс-объектов, они зафиксированы в исходных текстах программы и известны при редактировании файла ресурсов.

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

Подготовка файла описания ресурсов

Синтаксис языка описания ресурсов максимально приближен к языку С++. Это имеет практический смысл: при оформлении файла ресурсов можно "бесплатно" воспользоваться всей мощью препроцессора.

Семантика языка существенно беднее: по сути, весь файл описания разбивается на последовательность задания одиночных ресурсов в следующем виде:

<путь>: <имя> = <значение pecypca>;
или
<путь> { <имя_1>=<значение_1>;
<имя_2>=<значение_2>; ...}

Допустимы также формы вроде:

<путь_1>{ <путь_2>{ <имя_1>=<значение_1>;
<путь_3>, <путь_4>:<имя_2>=<значение_2> } }

В последнем примере ресурс с именем <имя_1> имеет составной путь <путь_1>.<путь_2>, а ресурс с именем <имя_2> - два пути: <путь_1>.<путь_2>.<путь_3> и <путь_1>.<путь_2>.<путь_4>.

Тип ресурса однозначно задается начальными буквами имени ресурса: например, sString iDX, msContain - это ресурсы, соответственно, типов "цепочка символов", "короткое целое" и "массив цепочек символов".

Значение ресурса имеет формат, соответствующий его типу:

sString = "Вот такая строка";
iDX = (150 + 3) & OxF3;
msContain = {"Первая строка
", "Вторая строка"};
miPrimes = {1, 2, 3, 5, 7, 11, 13, 17, 19, 23};

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

Путь <имя_n>.... <имя_2><имя_1> (без символа *) является подходящим для <объекта>, если выполняется одно из двух условий:

А) для любого i=1,...,n-1 <имя_1> совпадает либо с именем объекта, либо с именем класса -го предка объекта <объект>; -ый предок <объекта> является корневым;

Б) если <имя_1> совпадает с непустым ключом окружения <объекта>, то путь <имя_n>...<имя_2> является подходящим для <объекта> в смысле правила А).

Если в пути <путь_1> есть символ *, он является подходящим, если существует подходящий <путь_2>, из которого <путь_1> получается заменой одного или нескольких последовательных имен на символ *.

Рассмотрим, например, три следующих объекта:

имя объекта "grandPa" "father" "son"
имя класса "GrandPa" "Father" "Son"
объект-предок 0 grandPa father
ключ окружения 0 "Married" "Single"

Существует огромное число вариантов путей, "приводящих" к третьему по вложенности объекту "son". Вот некоторые варианты:

grandPa.father.son
grandPa.father.son.Single
GrandPa.Father.Son
grandPa.*.Single
*.Father.son
GrandPa.*
GrandPa.Father.Son.Single *

Отметим, что ключ окружения объекта "father" не может присутствовать в пути к объекту "son". Путь "*.Married" приводит именно к объекту "father" (либо к другим объектам, не указанным в примере, но, возможно, присутствующим в приложении, к которому относится пример).

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

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

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

Особенности работы с ресурсами

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

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

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

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

В качестве примера рассмотрим стандартную задачу оконного интерфейса - статуса "разрешен/запрещен" для строки из выпадающего меню. Было бы естественным полагать, что статус совпадает с некоторым ресурсом самого выпадающего меню. Однако изменение статуса ресурса происходит в модулях программы, выполняющих содержательные действия. Упоминать объект "выпадающее меню" в таких модулях довольно опасно и громоздко: конфигурация меню может резко модифицироваться. Гораздо проще завести в системе промежуточный ресурс-объект, отвечающий за запрещение и разрешение соответствующего действия, а в описании ресурсов предусмотреть разделение нужного ресурса между падающим меню и этиобъектом.

Профессия: коифигуратор

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

- В приведенном выше примере указано 10 различных путей, приводящих к одному и тому же объекту. На самом деле существует 54 варианта путей, приводящих к этому объекту, в которых используется не больше одного символа "*". И это в случае всего лишь трех объектов. Разнообразие возможностей при задании файла ресурсов очень большое.

- При использовании технологии ресурсов многие аспекты работы программы, ее внешнего вида могут быть до неузнаваемости изменены, в то время как исходные тексты на языках С и С++ останутся в первоначальном виде.

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

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

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

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