Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






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






Рассмотрим класс 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. Для манипуляций с методами необходимо использовать специальный процедурный тип, который называется указателем на метод (method pointer). Этот тип объявляется следующим образом:

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, передаваемый обработчику события, позволяет различать объекты, которые вызвали событие. Это помогает использовать один обработчик для разных объектов.


 

Литература

1. Архангельский, А. Delphi 2006. Справочное пособие. Язык Delphi, классы, функции Win32 и.NET / А. Я. Архангельский; – СПб.: Бином-Пресс, 2006. – 1152 с.: ил.

2. Кэнту, М. Delphi 2005. Для профессионалов / Марко Кэнту; пер. с англ. – СПб.: Питер, 2007. – 912 с.: ил.

3. Calvert, Ch. Object Pascal Style Guide / Charles Calvert [Электронный ресурс]. Режим доступа: https://edn.embarcadero.com/article/10280.


[1] Императив – повелительное наклонение в грамматике.

[2] Для имён полей в Object Pascal традиционно используется префикс «f» (field – поле).

[3] Объектная модель – правила реализации общих концепций ООП в конкретном языке программирования.

[4] Термин специфичен для Object Pascal, в котором статические методы противопоставляются виртуальным. В других языках программирования под статическим методом обычно понимают методы, работающие с классом, а не объектом.






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.