
Институт / Информатика или как там Агафонова вообщем / 1 / ООП в Delphi
.pdfend;
эквивалентно следующему:
type TPeople = class(TObject)
...
end;
Класс TObject выступает корнем любой иерархии классов. Он содержит ряд методов, которые по наследству передаются всем остальным классам. Среди них конструктор Create, деструктор Destroy, метод Free и некоторые другие методы.
Поскольку класс TObject является предком для всех других классов (в том числе и для ваших собственных), то не лишним будет кратко ознакомиться с его методами:
type
TObject = class constructor Create; procedure Free;
class function InitInstance(Instance: Pointer): TObject;
procedure CleanupInstance; function ClassType: TClass;
class function ClassName: ShortString;
class function ClassNameIs(const Name: string): Boolean;
class function ClassParent: TClass; class function ClassInfo: Pointer;
class function InstanceSize: Longint;
class function InheritsFrom(AClass: TClass): Boolean;
class function MethodAddress(const Name: ShortString): Pointer;
class function MethodName(Address: Pointer): ShortString;
function FieldAddress(const Name: ShortString): Pointer;
function GetInterface(const IID: TGUID; out Obj): Boolean;
class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
class function GetInterfaceTable: PInterfaceTable;
function SafeCallException(ExceptObject: TObject; ExceptAddr: Pointer): HResult; virtual;
procedure AfterConstruction; virtual;
procedure BeforeDestruction; virtual;
procedure Dispatch(var Message); virtual;
procedure DefaultHandler(var Message); virtual;
class function NewInstance: TObject; virtual;
procedure FreeInstance; virtual;
destructor Destroy; virtual;
end;
Краткое описание методов в классе TObject:
1. Create - стандартный конструктор.
2. Free - уничтожает объект: вызывает стандартный деструктор Destroy, если значение псевдопеременной Self не равно nil.
3. InitInstance(Instance: Pointer): TObject - при создании объекта инициализирует нулями выделенную память. На практике нет необходимости вызывать этот метод явно.
4.CleanupInstance - освобождает память, занимаемую полями с типом string, variant, динамический массив и интерфейс. На практике нет необходимости вызывать этот метод явно.
5.ClassType: TClass - возвращает описатель класса (метакласса).
6.ClassName: ShortString - возвращает имя класса.
7.ClassNameIs (const Name: string): Boolean - проверяет, является ли заданная строка именем класса.
8.ClassParent: TClass - возвращает описатель базового класса.
9.ClassInfo: Pointer - возвращает указатель на соответствующую классу таблицу RTTI. Таблица RTTI используется для проверки типов данных на этапе выполнения программы.
10.InstanceSize: Longint - возвращает количество байт, необходимых для хранения в памяти одного объекта соответствующего класса. Значение, возвращаемое этим методом и значение, возвращаемое функцией SizeOf при передачи ей в качестве аргумента объектной переменной - это разные значения. Функция SizeOf всегда возвращает значение 4 (SizeOf(Pointer)), поскольку объектная переменная - это ни что иное, как ссылка на данные объекта в памяти. Значение InstanceSize - это размер этих данных, а не размер объектной переменной.
11.InheritsFrom (AClass: TClass): Boolean - проверяет, является ли AClass
базовым классом.
12.MethodAdress (const Name: ShortString): Pointer - возвращает адрес published-метода, имя которого задаётся параметром Name.
13.MethodName (Address: Pointer): ShortString — возвращает имя published-метода по заданному адресу.
14.GetInterface (const IID: TGUID; out Obj): Boolean — возвращает ссылку на интерфейс через параметр Obj; идентификатор интерфейса задается параметром IID. (Интерфейсы рассмотрены в главе 6).
15.GetInterfaceEntry (const IID: TGUID): PInterfaceEntry — возвращает информацию об интерфейсе, который реализуется классом. Идентификатор интерфейса задается параметром IID.
16.GetInterfaceTable: PInterfaceTable — возвращает указатель на таблицу с информацией обо всех интерфейсах, реализуемых классом.
17.AfterConstruction — автоматически вызывается после создания объекта. Метод не предназначен для явного вызова из программы. Используется для того, чтобы выполнить определенные действия уже после создания объекта (для этого его необходимо переопределить в производных классах.
18.BeforeDestruction — автоматически вызывается перед уничтожением объекта. Метод не предназначен для явного вызова из программы. Используется для того, чтобы выполнить определенные действия непосредственно перед уничтожением объекта (для этого его необходимо переопределить в производных классах).
19.Dispatch (var Message) — служит для вызова методов, объявленных с ключевым словом message.
20.DefaultHandler (var Message) — вызывается методом Dispatch в том случае, если метод, соответствующий сообщению Message, не был найден.
21.NewInstance: TObject — вызывается при создании объекта для выделения динамической памяти, чтобы разместить в ней данные объекта. Метод вызывается автоматически, поэтому нет необходимости вызывать его явно.
22.FreeInstance — вызывается при уничтожении объекта для освобождения занятой объектом динамической памяти. Метод вызывается автоматически, поэтому нет необходимости вызывать его явно.
23.Destroy — стандартный деструктор.
24.FieldAdress (const Name: ShortString): Pointer — возвращает адрес published-поля, имя которого задается параметром Name.
В механизме наследования можно условно выделить три основных момента:
1.Наследование полей.
2.Наследование методов.
3.Наследование свойств.
Наследование свойств и методов имеет свои особенности. Свойство базового класса можно перекрыть в производном классе, например чтобы добавить ему новый атрибут доступа или связать с другим полем или методом. Метод базового класса тоже можно перекрыть в производном классе, например чтобы изменить логику его работы.
Для классов, связанных отношением наследования, вводится новое правило совместимости типов. Вместо объекта базового класса можно подставить объект любого производного класса. Обратное неверно. Правило совместимости классов чаще всего применяется при передаче параметров в параметрах процедур и функций. Все объекты являются представителями класса TObject. Поэтому любой объект любого класса можно использовать как объект класса TObject.
Поскольку реальный экземпляр объекта может оказаться наследником класса, указанного при описании объектной переменной или параметра, бывает необходимо проверить, к какому классу принадлежит объект на самом деле. Чтобы программист мог выполнять такого рода проверки, каждый объект хранит информацию о своём классе. В языке Delphi существуют операторы is и as, с помощью которых выполняется проверка на тип и преобразование к типу. Пример проверки на принадлежность объекта Obj к классу TPeople или его наследнику:
var Obj: TObject;
...
if Obj is TPeople then
...
Для преобразования объекта к нужному типу используется оператор as:
with Obj as TPeople do // Равносильно: with TPeople(Obj) do
Active := False;
Вариант с оператором as лучше, поскольку безопасен. Он генерирует ошибку (точнее исключительную ситуацию) при выполнении программы, если реальный экземпляр объекта Obj не совместим с классом TPeople.
Третий кит — это полиморфизм. Он означает, что в производных классах вы можете изменять работу уже существующих в базовом классе методов. При этом весь программный код, управляющий объектами родительского класса, пригоден для управления объектами дочернего класса без всякой модификации.
Виртуальные методы
Все методы, которые до сих пор рассматривались, являются статическими. Особенность такого метода заключается в его адресации. Она осуществляется ещё на стадии компиляции и компоновки проекта и будет неизменна (статична) до момента новой компиляции. Статическое связывание обладает существенным преимуществом над всеми остальными видами адресеции, поскольку обеспечивает самую высокую скорость вызова. Недостатком же фиксированной адресации является то, что статические методы не подлежат изменениям в классах-потомках. При обращении к статическому методу компилятор точно знает класс, к которому данный метод принадлежит. Объявление виртуального метода в базовом
классе выполняется с помощью ключевого слова virtual, а его перекрытие в производных классах - с помощью ключевого слова override. Перекрытый метод должен иметь точно такой же формат (список параметров, а для функций ещё и тип возвращаемого значения), что и перекрываемый:
type TPeople = class
Name: string;
procedure GetName; virtual; // Виртуальный метод
end;
type TStudent = class(TPeople)
...
procedure GetName; override;
end;
Суть виртуальных методов в том, что они вызываются по фактическому типу экземпляра, а не по формальному типу, записанному в программе. Работа виртуальных методов основана на механизме позднего связывания. В отличие от раннего связывания, характерного для статических методов, позднее связывание основано на вычислении адреса вызываемого метода при выполнении программы. Адрес метода вычисляется по хранящемуся в каждом объекте описателю класса. Благодаря механизму наследования и виртуальных методов в среде Delphi реализуется такая концепция объектноориентированного программирования как полиморфизм. Полиморфизм существенно облегчает труд программистов, поскольку обеспечивает повторное использование кода уже написанных и отлаженных методов.
Работа виртуальных методов основана на косвенном вызове подпрограмм. При косвенном вызове команда вызова подпрограммы оперирует не адресом подпрограммы, а адресом места в памяти, где хранится адрес подпрограммы. Для каждого виртуального метода создаётся процедурная переменная, но её наличие и использование скрыто от программиста. Все процедурные переменные с адресами виртуальных методов пронумерованы
и хранятся в таблице, называемой таблицей виртуальных методов. Такая таблица создаётся одна для каждого класса объектов, и все объекты этого класса хранят на неё ссылку.
Вызов виртуального метода происходит следующим образом:
1.Через объектную переменную выполняется обращение к занятому объектом блоку памяти.
2.Далее из этого блока извлекается адрес таблицы виртуальных методов (он записан в четырёх первых байтах).
3.На основании порядкового номера виртуального метода извлекается адрес соответствующей подпрограммы.
4.Вызывается код, находящийся по этому адресу.
При построении иерархии классов часто возникает ситуация, когда работа виртуального метода в базовом классе не известна и наполняется содержанием только в наследниках. Директива abstract записывается после
слова virtual и исключает необходимость написания кода виртуального метода для данного класса. Такой метод называется абстрактным, то есть подразумевает логическое действие, а не конкретный способ его реализации. Абстрактные виртуальные методы часто используются при создании полуготовых классов. Свою реализацию такие методы получают в законченных наследниках.
Разновидностью виртуальных методов являются динамические методы. При их объявлении вместо ключевого слова virtual записывается ключевое слово dynamic. Динамические методы перечислены в специальном списке отдельно от таблицы виртуальных методов. В список динамических
методов конкретного класса включены только адреса методов, описанных в данном классе. Поиск необходимого метода производится в обратном порядке дерева наследования. Если метод не найден в самом последнем дочернем классе, то поиск продолжается в его предке и так далее до TObject. В наследниках динамически методы перекрываются также, как и
виртуальные - с помощью зарезервированного слова override. Если вы по какой-либо причине забудете указать директиву override, то унаследованный метод будет скрыт (но не отменён). Если вы решили спрятать
предварительно объявленный метод, то поможет в этом директива reintroduce. Эта директива подавляет сообщения компилятора относительно уже существующего одноименного виртуального (динамического) метода в классе-предке. Задача команды inherited - принудительный вызов
унаследованного от предка метода из метода, переопределяемого потомком (вызов исходного метода родительского класса). По смыслу динамические и виртуальные методы идентичны. Различие состоит только в механизме их вызова. Виртуальные методы вызываются максимально быстро, но платой за это является большой размер системных таблиц, с помощью которых определяются их адреса. Размер этих таблиц начинает сказываться с увеличением числа классов в иерархии. Динамические методы вызываются несколько дольше, но при этом таблицы с адресами методов имеют более компактный вид, что способствует экономии памяти.
Специализированной формой динамических методов являются методы обработки сообщений. Они объявляются с помощью ключевого слова message, за которым следует целочисленная константа - номер сообщения. Пример из библиотеки VCL:
type TWidgetControl = class(TControl)
...
procedure CMKeyDown (var Msg: TCMKeyDown); message CM_KeyDown;
...
end;
Метод обработки сообщений имеет формат процедуры и содержит единственный var-параметр. При перекрытии такого метода название метода и имя параметра могут быть любыми, важно лишь, чтобы
неизменным остался номер сообщения, используемый для вызова метода. Вызов метода выполняется не по имени, как обычно, а с помощью обращения к специальному методу Dispath, который имеется в каждом классе. Методы обработки сообщений применяются внутри библиотеки VCL для обработки команд пользовательского интерфейса и редко нужны при написании прикладных программ.