32 урока по Delphi
 

Урок 26: Новые концепции ООП в Object Pascal

Данный материал принадлежит Сергею Орлику. ?1996 Все права защищены.
 
 

В языке Object Pascal, используемом в Delphi, произошел ряд давно ожидаемых программистами изменений, по сравнению с последней версией Borland Pascal. Перечислим, основные из них, позволившие назвать объектную модель Object Pascal новой объектной моделью:

В отличие от объявления старых объектных типов, использовавшего ключевое слово object, новые объектные типы определяются с помощью слова class. Здесь уместно привести определение отношения между понятиями объекта и класса, данное Гради Бучем:
 
 
 
 

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

{ Старая объектная модель }

type

PMyObject = ^TMyObject;

TMyObject = object (TObject)

MyField : PMyType;

constructor Init;

end;

...

var

MyObject : PMyObject;

begin

MyObject:=New(PMyObject,Init);

MyObject^.MyField:= ...

end;

{ Новая объектная модель }

type

TMyObject = class (TObject)

MyField : TMyType;

constructor Create;

end;

...

var

MyObject : TMyObject;

begin

MyObject:=TMyObject.Create;

MyObject.MyField:= ...

end;

Как Вы могли заметить, в Object Pascal расширен синтаксис использования “точечной нотации” для доступа к методам объектов. Кроме того, изменено соглашение и об именовании конструкторов и деструкторов. В старой объектной модели вызов New отвечал за распределение памяти, а обращение к конструктору инициализировало выделенную область памяти. В новой модели эти функции выполняет конструктор Create.

Приведем объявление базового для всех объектных типов класса TObject:

TObject = class

constructor Create;

destructor Destroy; virtual;

procedure Free;

class function NewInstance: TObject; virtual;

procedure FreeInstance; virtual;

class procedure InitInstance(Instance: Pointer): TObject;

function ClassType: TClass;

class function ClassName: string;

class function ClassParent: TClass;

class function ClassInfo: Pointer;

class function InstanceSize: Word;

class function InheritsFrom(AClass: TClass): Boolean;

procedure DefaultHandler(var Message); virtual;

procedure Dispatch(var Message);

class function MethodAddress(const Name: string):

Pointer;

class function MethodName(Address: Pointer): string;

function FieldAddress(const Name: string): Pointer;

end;
 
 

Компилятор Object Pascal является основой Delphi. Визуальные же средства Delphi построены на концепции Two-Way Tools, позволяющей синхронизировать процесс визуального проектирования форм приложения с генерацией исходного кода.
 
 

Такая архитектура возможна только при наличии механизма поддержки информации о типах - RTTI (RunTime Type Information). Основой такого механизма является внутренняя структура классов и, в частности, возможность доступа к ней за счет использования методов классов, описываемых конструкцией class function... Дадим определение понятия метода класса:
 
 
 
 

С одной стороны, Delphi, будучи визуальной средой разработки приложений, ориентирован на тех программистов, которые из готовых компонент "собирают" конкретные приложения для конечных пользователей. С другой стороны, являясь расширяемым объектно-ориентированным инструментом, этот продукт представляет интерес и для специалистов, занимающихся наращиванием функциональных возможностей уже существующих программных библиотек. Поэтому, выглядит абсолютно логичным появление в Object Pascal новых разделов в описании классов, соответственно, published и protected. Вместе с ранее введенными разделами (public и private) они предоставляют полный контроль над возможностями использования и "безболезненной" (в смысле предотвращения фатальных с точки зрения идеологии ошибок) модификации компонент Visual Component Library (VCL - библиотека классов Delphi). Чтобы была более ясна логика использования новых разделов, дадим, также, краткую характеристику и уже существующих:

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

Раздел protected комбинирует функциональную нагрузку разделов private и public таким образом, что, если вы хотите скрыть внутренние механизмы вашего объекта от конечного пользователя, этот пользователь не сможет в run-time использовать ни одно из объявлений объекта из его protected области, но это не помешает разработчику новых компонент использовать эти механизмы в других модулях. То есть, protected-объявления доступны у любого из наследников вашего класса.

Раздел published оказался необходимым при введении в Delphi возможности установки свойств и поведения компонент еще на этапе конструирования форм и самого приложения. Именно published-объявления доступны через Object Inspector, будь это ссылки на свойства или обработчики событий.

Следует отметить тот факт, что, при порождении нового класса, возможен перенос объявлений из одного раздела в другой, с единственным ограничением - если вы производите скрытие объявления за счет его переноса в раздел private - в дальнейшем его "вытаскивание" у наследника в более доступный раздел в другом модуле будет уже невозможен. Такое ограничение, к счастью, не распространяется на динамические методы-обработчики сообщений Windows.

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

Методы объектов Object Pascal могут иметь любой из трех типов: статический, виртуальный или динамический.

Так как статические и виртуальные методы не претерпели принципиальных изменений, по сравнению с Borland Pascal 7.0, остановимся на новом по реализации типе - динамическом (который, вообще говоря, присутствовал в неявном форме в библиотеке OWL).

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

В отличие от виртуальных методов и самой идеологии VMT, таблица динамических методов (DMT) содержит входы только для методов, объявленных или перекрытых для данного класса. На каждый динамический метод приходится только одна ссылка, представленная так называемым "индексом", по которому и происходит поиск метода для вызова (базовая информация по обработке динамических методов содержится в модуле x:\delphi\source\rtl\sys\dmth.asm). C точки зрения синтаксиса, перекрытие динамических и виртуальных методов производится одинаково - с использованием ключевого слова override. Исключение составляют обработчики Windows-сообщений wm_Xxx.

В Delphi существуют понятия, принципиально новые для уже существующих объектно-ориентированных реализаций Pascal. К числу этих понятий относятся свойства, функция класса и объектная ссылка.

В Object Pascal добавлена возможность определения полей процедурного типа. Очевидно, что в теле функций привязываемых к этим полям, разработчику необходим доступ к другим полям объекта, методам и т.п. Возможность такого доступа базируется на передаче в эти функции неявного, но доступного в их коде, параметра, автоматически принимающего значение поля объекта Self. Такие функции называются функциями классов. Для объявления функций классов необходимо использовать специальную конструкцию function ... of object.

Delphi позволяет вам создать специальный описатель объектного типа (именно типа, а не на экземпляра !), известный как object reference - объектная ссылка.

Объектные ссылки используются в следующих случаях:

Объектная ссылка определяется с использованием конструкции class of... . Приведем пример объявления и использования class reference:

type

TMyObject = class (TObject)

MyField:TMyObject;

constructor Create;

end;

TObjectRef = class of TObject;

...

var

ObjectRef:TObjectRef;

s:string;

begin

ObjectRef:=TMyObject; {присваиваем тип, а не экземпляр !}

s:=ObjectRef.ClassName; { строка s содержит ‘TMyObject’ }

end;

Таким образом в Delphi определена специальная ссылка TClass, совместимая по присваиванию с любым наследником TObject. Аналогично объявлены TPersistentClass и TComponentClass.

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

type

TMyObject = class (TObject)

procedure One;

procedure Two; virtual;

class procedure Three; virtual;

end;

TMyClass = class of TObject;

...

var

MyObject:TMyObject;

MyClass:TMyClass;

вызов MyObject.One сгенерирует следующий код:

les DI,MyObject

push ES

push DI

call MyObject.One

При возврате управления, метод должен удалить из стека вначале параметр Self, а затем и остальные - явные параметры.

Методы всегда используют дальнюю (far) модель вызова, несмотря на то, каким образом установлена директива компиляции $F. Для вызова виртуального метода, компилятор генерирует код, загружающий из объекта указатель на VMT, и затем вызывает через точку входа в VMT ( ES:[DI] ), ассоциированный с этой точкой входа метод. Например, вызов MyObject.Two приведет к генерации следующего:

les DI,MyObject

push ES

push DI

les DI, ES:[DI]

call DWORD PTR ES:[DI]

Вызов MyObject.Three :

les DI,MyObject

les DI, ES:[DI]

push ES

push DI

call DWORD PTR ES:[DI+4]

; +4, т.к. это смещение в VMT для второго

; по счету виртуального метода

А для MyClass.Three будет сгенерированно:

les DI,MyClass

push ES

push DI

call DWORD PTR ES:[DI+4]

Очевидно, что приведенные примеры генерируемого кода соответствуют 16-разрядной версии Delphi, но по своей идеологии они остаются верной и для Delphi32.