7.2 Разработка

Предыдущая  Содержание  Следующая V*D*V

Давайте сделаем несколько выводов из эксперимента препроцессирования Point. Мы начали с довольно простого, строчно-ориентированного описания класса:

 

строки для интерфейса                      // произвольные

%prot

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

 

% метакласс[: метасупер] класс: супер {    // заголовок

    ...                                    // компоненты объектов

%                                          // статически компонуемые

    тип имя ([const] _self, ...);

    ...

%-                                         // динамически компонуемые

    тип имя ([const] _self, ...);

    ...

%}

 

Единственная трудность состоит в том, что нам надо распутать списки параметров и разделить информацию о типе и об имени в каждой декларации. Дешёвое решение состоит в том, чтобы потребовать, чтобы const предшествовал типу, если таковой имеется, и чтобы тип предшествовал имени (* При необходимости это всегда может быть организовано с помощью typedef.). Также распознаются следующие особые случаи:

 

_self

цель сообщения в текущем классе

_name

другой объект в текущем классе

class @ name

объект в другом классе

 

Всем им может предшествовать const. Объекты в текущем классе разыменовываются после импортирования.

Именем файла для описания класса является имя класса с последующим .d, поэтому ooc может найти описания суперклассов. Мы не должны сильно беспокоиться о метаклассе: либо класс имеет тот же самый метакласс, как и его суперкласс, либо класс имеет новый метакласс и суперкласс этого метакласса является метаклассом суперкласса класса. В любом случае, если мы прочитаем описание суперкласса, то получим достаточно информации, чтобы справиться с метаклассом.

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

 

$ ooc Point -h > Point.h # файл интерфейса

$ ooc Point -r > Point.r # файл представления

 

Файл реализации является более трудным. ooc должен как-то узнать о телах методов и должны или нет параметры быть проверены и импортированы. Если мы добавим тела методов в файл описания класса, мы сохраним всё вместе, но потребуется гораздо больше обработки: во время каждого запуска ooc должен загрузить файлы описаний классов обратно в корневой класс, тем не менее, тела методов интересны только в самом внешнем файле описания. Кроме того, реализации изменятся скорее, чем интерфейсы; если тела методов сохраняются с описанием класса, make будет заново создавать интерфейсные файлы каждый раз, когда мы меняем тело метода, и это, вероятно, приведёт к большому количеству ненужных перекомпиляций. (* yacc имеет аналогичную проблему с файлом заголовка y.tab.h. Стандартным подходом является дублирование этого файла, переписывание копии только если она является новой, и использование этой копии в правилах makefile. См. [K&P84].)

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

 

$ ooc Point -dc > skeleton # начало реализации

 

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

ooc должен быть инструментом разработки и сопровождения. Если мы изменим заголовок метода в описании класса, очень разумно ожидать, что ooc распространит это изменение по всем файлам реализации. Используя вариант со скелетом мы сможем лишь создавать новый скелет и вручную редактировать всё вместе — не очень приятная перспектива!

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

 

$ ooc Point Point.dc > Point.c # работа препроцессора

 

ooc загружает описание класса для Point, а затем читает файл реализации Point.dc и записывает препроцессированную версию этого файла в поток стандартного вывода. Мы можем даже совместить это с подходом скелета, описанного выше, до тех пор, пока мы создаём скелет с командами препроцессора, а не как неизменяемый файл C.

 

Предыдущая  Содержание  Следующая