7.2 Разработка |
Предыдущая Содержание Следующая |
Давайте сделаем несколько выводов из эксперимента препроцессирования Point. Мы начали с довольно простого, строчно-ориентированного описания класса:
строки для интерфейса // произвольные %prot строки для представления
% метакласс[: метасупер] класс: супер { // заголовок ... // компоненты объектов % // статически компонуемые тип имя ([const] _self, ...); ... %- // динамически компонуемые тип имя ([const] _self, ...); ... %}
Единственная трудность состоит в том, что нам надо распутать списки параметров и разделить информацию о типе и об имени в каждой декларации. Дешёвое решение состоит в том, чтобы потребовать, чтобы const предшествовал типу, если таковой имеется, и чтобы тип предшествовал имени (* При необходимости это всегда может быть организовано с помощью typedef.). Также распознаются следующие особые случаи:
Всем им может предшествовать 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.
| ||||||
Предыдущая Содержание Следующая |