Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ООП на языке Object Pascal.docx
Скачиваний:
22
Добавлен:
11.05.2015
Размер:
79.62 Кб
Скачать

9. Rtti и размещение объектов в памяти

В Object Pascal после компиляции программы для любого класса сохраняется некая дополнительная информация, которая размещается в памяти непосредственно перед VMT. Эта информация называется информацией о типе периода времени выполнения(run-timetypeinformation, RTTI). Как было сказано выше, любой объект кроме данных полей содержит указатель на VMT (возможно на пустую таблицу, если у класса и его предков нет виртуальных методов). Следовательно, во время работы программы любой объект может получить доступ к RTTI своего класса. Схема размещения объектов и класса в памяти показана на рис. 1.

Рис. 1. Схема размещения объектов и RTTIв памяти

Известно, что в RTTI в числе прочих содержатся следующие данные:

1. Указатель на VMT класса-предка;

2. Указатель на строку с именем класса;

3. Размер экземпляра объекта в байтах.

Эти данные позволяют во время выполнения программы контролировать(type checking) иприводить(type casting) объектные типы.

Для контроля типов используется оператор is. Выражениеобъект is классвозвращаетtrue, еслиобъектпринадлежит указанному классу или потомкам этого класса:

if Man is TPerson then . . .

Для приведения типов используется оператор asв следующей форме:

(Man as TPerson).SetAge(10);

Допустима традиционная конструкция приведения типов в виде TPerson(Man).SetAge(10), однако операторasявляется более безопасным. В случае неудачи (то есть, когда объект не относится к указанному классу или его потомкам) он генерирует обрабатываемую исключительную ситуацию, а жёсткое приведение типов может привести к краху приложения.

10. Обработчики событий и указатели на методы

Рассмотрим класс TPersonи его методSetAgeдля установки возраста:

type TPerson = class

. . .

procedure SetAge(Age: Integer);

end;

procedure TPerson.SetAge(Age: Integer);

begin

if Age > 0 then fAge := Age

end;

Представим, что нужно дать пользователю контроль над ситуацией, когда значение параметра Ageне подходит для установки поля. Употребляя терминологию ООП, можно сказать: мы хотим дать пользователю контроль над определённымсобытием(неправильное значение параметра), происходящим при работе с объектом. Мы собираемся разрешить пользователю иметь обработчик данного события. Одно из решений, которое можно предложить в этой ситуации, заключается в следующем: поместим в классTPersonвиртуальный метод, вызываемый в результате события:

type TPerson = class

. . .

procedure SetAge(Age: Integer);

procedure DoSomething;virtual;

end;

procedure TPerson.SetAge(Age: Integer);

begin

if Age > 0 then fAge := Age

else DoSomething

end;

procedure TPerson.DoSomething;

begin

end;

Теперь для того, чтобы обработать событие, пользователю достаточно породить дочерний класс от TPersonи перекрыть методDoSomething:

type TMyPerson = class(TPerson)

procedure DoSomething;override;

end;

procedure TMyPerson.DoSomething;

begin

writeln('Something is wrong!')

end;

Такой подход для обработки событий имеет недостатки. Пользователи вынуждены «плодить» многочисленные производные классы, усложняя логику программы. Если бы в предыдущем примере планировалось создать десять объектов с разной обработкой события, мы вынуждены были бы породить от TPersonдесять дочерних классов.

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

type TProc = procedure;

type TPerson = class

. . .

fEvent: TProc;

procedure SetAge(Age: Integer);

property Event: TProc read fEvent write fEvent;

end;

procedure TPerson.SetAge(Age: Integer);

begin

if Age > 0 then fAge := Age

else fEvent // желательно дополнительно проверять fEvent на nil

end;

var Man, Woman: TPerson;

procedure ManEvent;

begin

writeln('Something is wrong with my age')

end;

procedure WomanEvent;

begin

writeln('I am 18 y.o.!')

end;

. . .

Man := TPerson.Create; // создание объектов

Woman := TPerson.Create;

Man.Event := ManEvent; // назначение обработчиков событий

Woman.Event := WomanEvent;

Man.SetAge(-10); // здесь эти обработчики сработают

Woman.SetAge(-10);

Подобный подход лучше, чем порождение многочисленных дочерних классов, однако он является не совсем объектно-ориентированным. По принципам «чистого» ООП программа – это набор взаимодействующих объектов различных классов. В нашем случае процедуры обработки события не являются методами класса, а «висят в воздухе». Попробуем объединить их в специальный класс, чтобы соблюсти принципы объектно-ориентированной разработки:

type TEventClass = class

procedure ManEvent;

procedure WomanEvent;

end;

procedure TEventClass.ManEvent;

begin

writeln('Something is wrong with my age')

end;

procedure TEventClass.WomanEvent;

begin

writeln('I am 18 y.o.!')

end;

Однако в таком варианте попытка назначить объектам обработчики вызывает синтаксическую ошибку:

var Man, Woman: TPerson;

EventObject: TEventClass;

. . .

EventObject := TEventClass.Create;

Man.Event := EventObject.ManEvent; // синтаксическая ошибка!

Хотя метод TEventClass.ManEventимеет сигнатуру типаTProc, он (как любой метод) получает неявный параметрself. Для манипуляций с методами необходимо использовать специальный процедурный тип, который называетсяуказателем на метод(methodpointer). Этот тип объявляется следующим образом:

type TProc = procedure of object;

Конструкцияof objectпосле сигнатуры подпрограммы говорит об объявлении указателя на метод. Переменная типа указатель на метод занимает 8 байтов и содержит адрес метода и ссылку на объект (т. е. значениеselfдля конкретного объекта). Если изменить объявление типа поля и свойства в классеTPersonна тип указателя на метод, то для обработки событий можно будет использовать методы другого класса (при условии совпадения сигнатур):

type TProc = procedure of object;

type TPerson = class

. . .

fEvent: TProc;

procedure SetAge(Age: Integer);

property Event: TProc read fEvent write fEvent;

end;

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

Обработка событий и делегирование эффективно используется в Delphi. Любой компонент, помещаемый на форму при проектировании, представляет собой объект некоего класса. Компоненты обладают многочисленными событиями. Тип простейших событий с единственным параметром Senderописан следующим образом:

type TNotifiEvent = procedure(Sender: TObject) of object;

В качестве примера рассмотрим класс TEdit. Он обладает событиемOnClick. Для реализации работы с ним классTEditсодержит полеFOnClickи свойствоOnClick.

type TEdit = class(TCustomEdit)

. . .

FOnClick: TNotifiEvent;

property OnClick: TNotifiEvent read FOnClick

write FOnClick;

end;

Если в процессе разработки мы помещаем на форму компонент Edit1: TEditи пишем для него обработчик событияOnClick, то фактически мы пишем новый метод класса формыTForm1.Edit1Click. При создании формы в начале выполнения приложения метод формы связывается со свойством компонента (без нашего участия):

Edit1.OnClick := Form1.Edit1Click; // Form1 – объект классаTForm1

При щелчке на компоненте Edit1системой вызывается методTEdit.Click. В нем производится проверка наличия обработчика событияOnClickи вызов этого обработчика, если он существует.

if Assigned(OnClick) then OnClick(self);

Функция Assignedвозвращает значениеtrue, если её аргумент не равенnil. ПараметрSender, передаваемый обработчику события, позволяет различать объекты, которые вызвали событие. Это помогает использовать один обработчик для разных объектов.