Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
HOR / TOTAL.DOC
Скачиваний:
8
Добавлен:
16.04.2013
Размер:
312.83 Кб
Скачать

5. Разработка отображения для языкаObject Pascal.

При обеспечении возможности работы с системой из интегрированной среды разработки Delphi возникла потребность разработки отображения языкаIDL для языкаObject Pascal, используемого вDelphi и являющегося дальнейшим развитием языкаTurbo Pascal, используемого в первых компиляторах фирмыBorland International. При разработки отображения пришлось столкнуться с рядом проблем, которые были успешно преодолены. Далее в этой главе описаны наиболее показательные проблемы, которые нельзя было преодолеть стандартными средствами языка и способы, которыми они были решены.

5.1. Множественное наследование.

При разработке отображения для языка программирования Object Pascal возникла проблема с реализацией множественного наследования. СпецификацияCORBAдопускает множественное наследование объектов. Поэтому возникла потребность в эмуляции множественного наследования. Рассмотрим основные характеристики множественного наследования и их реализацию.

Несколько базовых классов.

На рисунке 5-1 показан пример множественного наследования. КлассClassC наследуется сразу от двух классов - классаClassA и классаClassB.

Рисунок 5-1. Множественное наследование.

Само множественное наследования реализовано следующим образом. Если некоторый класс (назовем его основным) наследуется от двух и более классов, то он на самом деле наследуется от первого класса, а для остальных классов он является контейнером, то есть объекты этих классов просто определяются как поля нового класса. Экземпляры этих (дополнительных) классов создаются в конструкторе при создании основного класса и уничтожаются при его уничтожении. Рисунок 5-2 иллюстрирует сказанное.

Рисунок 5-2. Псевдомножественное наследование вObject Pascal.

Таким образом новый класс инкапсулирует все данные базовых классов. Для обеспечения же инкапсуляции и методов базовых классов нужно предпринять некоторые дополнительные действия.

Наследование методов базовых классов.

Предположим, что класс ClassA имеет методMethodA, а классClassB - методMethodB. Тогда классClassC, производный от этих двух классов автоматически наследует методы от обоих классов, то есть у объектов этого класса можно вызывать как методMethodA так и методMethodB(рисунок5-3).

Рисунок 5-3. Наследование методов.

В языке Object Pascal у основного класса создаются все методы, которые имеются у дополнительных классов с теми же самыми параметрами. При вызове метода просто осуществляется перевызов метода у соответствующего инкапсулированного объекта (рисунок5-4).

Рисунок 5-4. Наследование методов вObject Pascal.

Так как IDL, используемый в спецификацииCORBAне позволяет определять у класса поля, то этот аспект эмуляции множественного наследования был благополучно проигнорирован. Вместо полейCORBA определяет так называемые атрибуты, которые прекрасно ложатся к концепцию свойствObject Pascal, для которых все производится аналогично функциям.

Преобразование типов.

Преобразование типов вызвало наибольшие затруднения. В рассмотренной ситуации если имеется экземпляр объекта класса ClassC, то он может быть преобразован как к типуClassA, так и к типуClassB. Такое преобразование называется преобразованием вниз по дереву наследования и доступно в любом случае.

Далее, если имеется переменная, указывающая на класс ClassAили классClassB, то можно попытаться выполнить преобразование к типуClassC и в том случае, если такое преобразование возможно (то есть в действительности объект классаClassC был преобразован к одному из своих базовых классов), то в результате преобразования должна возвратиться ссылка на объект классаClassC.Такое преобразование называется преобразованием вверх по дереву наследования и проверка корректности преобразования возможно только в том случае, если компилятор допускает получение информации о типе в момент выполнения(Run-Time Type Information - RTTI).

В качестве частного случая можно попытаться преобразовать указатель на объект класса ClassA к указателю на объект классаClassB посредством промежуточного преобразования к классуClassC.

Компилятор Delphi поддерживаетRTTI, но разумеется для случая одиночного наследования. Зато на основании этого возможна реализация преобразования типов, удовлетворяющих множественному наследованию.

У базового класса InterfacedObject, от которого далее наследуются все классы имеется свойство (свойство можно рассматривать просто как поле объекта)Instanceтоже типаInterfacedObject, которое указывает на класс-контейнер. При обычном (одиночном) наследовании это поле имеет значениеnil. Если же имеет место эмуляция множественного наследования, то класс контейнер при создании экземпляров дополнительных классов устанавливает это свойство таким образом, чтобы оно указывало на него.

Базовый класс InterfacedObjectтакже объявляет виртуальную функциюAsClass, которая имеет один параметр - тип, к которому надо преобразовать объект. При успешном преобразовании она возвращает указатель на классInterfacedObject, который может быть преобразован к указателю запрашиваемого типа. Если же требуемое преобразование недопустимо, то возникает исключение неправильного преобразования типов. МетодAsClass реализован в классеInterfacedObjectследующим образом:

functionInterfacedObject.AsClass(

Interfaced: InterfacedClass;

AllowNarow: Boolean): InterfacedObject;

begin

Result := nil;

if Self is Interfaced then Result := Self;

if (Result = nil) and (Instance <> nil)

and AllowNarrow

then Result := Instance.AsClass(Interfaced, True);

end;

Не вдаваясь в специфику нововведений языка Object Pascal, касающихсяRTTI, алгоритм можно описать следующим образом. Сначала производится попытка преобразования самого экземпляра объекта к запрашиваемому типу. Если преобразование успешно, то возвращается полученный указатель. В случае же неудачного преобразования проверяется значение свойстваInstance. Если оно имеет значение, отличное отnil, то вызывается методAsClass для класса-контейнера. В противном случае возвращается значениеnil, которое свидетельствует о неудачном преобразовании. ПараметрAllowNarrowнужен для предотвращения зацикливания. Его роль будет рассмотрена несколько позднее.

При таком алгоритме для классов, реализующих одиночное наследование не требуется перекрывать этот метод. Если же класс эмулирует множественное наследование, то в нем следует перекрыть данный метод и в случае неудачной попытки преобразования самого себя в запрашиваемому типу перед обращением к свойству Instanceследует попытаться преобразовать к данному типу один из инкапсулированных объектов. Например, для рассмотренного выше случая методAsClass у классаClassC следует определить следующим образом:

functionClassC.AsClass(

Interfaced: InterfacedClass;

AllowNarow: Boolean): InterfacedObject;

begin

Result := inherited AsClass(Interfaced, False);

if Result = nil

then Result := FClassB.AsClass(Interfaced, False);

if (Result = nil) and (Instance <> nil)

andAllowNarrow

then Result := Instance.AsClass(Interfaced, True);

end;

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

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

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

class function ClassC.Narrow(

Obj: InterfacedObject): ClassC;

begin

Result := ClassC(Obj.AsClass(ClassC, True));

end;

Тогда для преобразования некоторого объектного типа как вниз, так и вверх по дереву наследования, необходимо вызвать функцию Narrow этого класса:

var

Obj: InterfacedObject;

ObjC: ClassC;

...

ObjC := ClassC.Narrow(Obj);

if ObjC = nil then

begin

{ преобразование недопустимо }

end;

...

Недостатки данной реализации.

Имеющаяся реализация псевдомножественного наследования имеет некоторые недостатки. Самым большим недостатком является отсутствие механизма аналогичного виртуальным базовым классам в языке C++. Смысл виртуальных базовых классов заключается в следующем. Если бы оба наши базовых классаClassA иClassBбыли бы унаследованы еще от некоторого классаClassXобычным образом, то классClassC в конечном счете должен унаследовать две копии объектов этого класса - один через классClassAи один через классClassB(рисунок5-5).

При этом непонятно, каким образом выполнять преобразование объекта класса ClassC к типуClassX - использовать ли копию, находящуюся в классеClassA или классеClassB. ЯзыкC++ в рассмотренной ситуации не допускает выполнение прямого преобразования - необходимо явно преобразовать объект к одному из типовClassA илиClassB, а уже потом выполнять преобразование к типуClassX. Но реализованная эмуляция в данной ситуации выполнит преобразование к первой допустимой копии в порядке наследования классов, то есть к копии, находящейся в классеClassA.

Рисунок 5-5. Статическое наследование.

Язык C++ для устранения данной проблемы предлагает механизм виртуальных базовых классов, который гарантирует, что при любой иерархии наследования экземпляр итогового класса будет иметь только одну копию объекта данного класса. В этом случае дерево наследования будет выглядеть так, как показано на рисунке5-6. При таком наследовании преобразование экземпляра классаClassC к типуClassX проблем не вызывает и всегда однозначно разрешается.

Рисунок 5-6. Виртуальное наследование.

Такой способ не поддерживается по нескольким причинам. Во-первых пришлось бы заводить дополнительные механизмы для определения экземпляров виртуальных базовых классов, что представляет определенные трудности и в результате требовало бы сложного алгоритма определения местонахождения копии объекта требуемого класса. Во-вторых спецификация CORBAникак не определяет механизм виртуального наследования, везде предполагая статическое. Следовательно, все доступныеORB-у классы для обеспечения ожидаемого от них поведения должны наследоваться статически, так как в случае наследования распределенных классов виртуальное наследование вызвало бы массу проблем. Таким образом, виртуальное наследование ввиду его громоздкости и отсутствия потребности в этом при обеспечении отображения для языкаObject Pascal не реализовано.

Соседние файлы в папке HOR