Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
OOP_answers (1).docx
Скачиваний:
1
Добавлен:
01.03.2025
Размер:
2.9 Mб
Скачать

7. Понятие ссылки на метод объекта (или делегата – в зависимости от языка программирования). Понятие события. Применение ссылок на методы для расширения объектов.

Понятие ссылки на метод объекта

Delphi:

Существуют процедурные типы данных для методов объектов.

type

TReadLineEvent = procedure(Reader: TTextReader; constLine: string) of object;

Переменная такого типа – указатель на метод (method pointer). Она занимает в памяти 8 байт и хранит одновременно ссылку на объект и адрес его метода.

type

TTextReader = class

private

FOnReadLine: TReadLineEvent;

...

public

property OnReadLine: TReadLineEvent read FOnReadLine write FOnReadLine;

end;

Методы объектов, объявленные по приведенному выше шаблону, становятся совместимы по типу со свойством OnReadLine.

Получается, что один объект может получить какие-то уведомления от другого объекта. Такой механизм называется делегированием, поскольку он позволяет передать часть работы другому объекту, например, сосредоточить в одном объекте обработку событий, возникающих в других объектах. Это избавляет программиста от необходимости порождать многочисленные классы-наследники и перекрывать в них виртуальные методы. Делегирование широко применяется в среде Delphi. Например, все компоненты делегируют обработку своих событий той форме, в которую они помещены.

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

В следующем примере объявляется и используется тип делегата Function.

delegate double Function(double x);

class Multiplier { double factor;

public Multiplier(double factor)

{ this.factor = factor; }

public double Multiply(double x)

{ return x * factor; } }

class Test { static double Square(double x)

{ return x * x; }

static double[] Apply(double[] a, Function f)

{ double[] result = new double[a.Length]; for (int i = 0; i < a.Length; i++) result[i] = f(a[i]); return result; }

static void Main()

{ double[] a = {0.0, 0.5, 1.0};

double[] squares = Apply(a, Square);

double[] sines = Apply(a, Math.Sin);

Multiplier m = new Multiplier(2.0); double[] doubles = Apply(a, m.Multiply); } }

Делегат может ссылаться как на статический метод, так и на метод экземпляра. Делегат, ссылающийся на метод экземпляра, также ссылается на конкретный объект. При вызове такого метода экземпляра с помощью делегата этот объект становится объектом this.

Делегаты также могут создаваться с помощью анонимных функций, которые представляют собой «встроенные методы», создаваемые в процессе выполнения. Анонимные функции могут видеть локальные переменные окружающих их методов. Таким образом, приведенный выше пример множителя может быть записан проще с использованием класса Multiplier:

double[] doubles = Apply(a, (double x) => x * 2.0);

Интересной и полезной особенностью делегата является то, что для него неважен тип класса или метода, на который он ссылается. Единственным требованием является наличие у метода, на который ссылается делегат, такого же числа параметров и типа возвращаемого значения.

Понятие события

Наиболее общее определение события – любое происшествие, вызванное вмешательством пользователя, поведением системы или логикой кода приложения. Событие в программе может быть связано с некоторым кодом, называемым обработчиком события и отвечающим на это происшествие. С точки зрения разработчика, событие – это имя, к которому обработчик события может быть присоединен в программе.

Если разработчик реализовал код обработчика события, то он выполняется, когда это событие происходит. В противном случае обычно ничего не происходит.

Имеется два вида событий: взаимодействие пользователя и изменение состояния. События взаимодействия пользователя почти всегда вызываются в соответствии с сообщениями Windows, указывая, что пользователь делал кое-что, поэтому компонент может ответить. События изменения состояния также могут быть сведены к сообщениям Windows.

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

События уведомления просто сообщают, что некоторое событие случилось, без предоставления информации о том, где и когда. Уведомления обычно использую тип TNotifyEvent, который имеет только один параметр – отправитель события. Специфические события отличаются от событий уведомления, и используются в тех случаях, когда хотелось бы знать, не только какое событие случилось и какой компонент его вызвал, но и получить сопутствующую дополнительную информацию. В подобных случаях необходимы обработчики, которые включают параметры для дополнительной информации. В подобных случаях нужны обработчики специфических событий.

Синтаксически события объявляются почти полностью аналогично свойствам:

Property <имя события>: <указателъ на метод>

[Read <поле события метод чтения>]

[Write <поле события\метод записи>];

• Хотя в командах чтения и записи могут использоваться методы, на практике они не используются. Обычно используется прямой доступ к полю.

• Все события в Delphi принято именовать: OnCreate, OnClick и т.д.

В данном случае событие и соответствующее поле имеют тип TNotifyEvent.

Таким образом, в общем случае для объявления события необходимо сделать следующее:

• определить или выбрать тип данных события (тип указателя на метод) (процедурный тип);

• объявить поле соответствующего типа для хранения значения события;

• объявить (корреспондирующий – event-dispatching method) метод, который будет вызывать обработчик события, когда оно произойдет. Представить реализацию этого метода и определить механизм его вызова;

Стандартный синтаксис для реализации метода уведомления о событии имеет следующий вид:

Procedure <имя класса>.<имя метода>[(<объявление параметров>)];

Begin

<некий код> If Assigned(<пoлe события>)

Then <поле coбытия>(Self[,<дополнительные параметры>]);

<некий код>

End;

type

TNotifyEvent = procedure(Sender : TObject) of object;

TControl = class(TComponent)

private

FOnClick : TNotifyEvent;

protected

procedure Click; dynamic;

property OnClick : TNotifyEvent read FOnClick write FOnClick;

end;

procedure TControl.Click;

begin

if Assigned(FOnClick) then

FOnClick(Self);

end;

Класс TButton наследуется от TControl и публикует свойство OnClick. Object Inspector, видя свойство процедурного типа, размещает его на странице обработчиков событий. При нажатии на кнопку класс TButton вызывает метод Click, и тот вызывает соответствующий обработчик события, если последний присвоен.

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

procedure ButtonClick (Self : TButton; Sender : TButton);

begin

ShowMessageFmt ('Нажата кнопка %s', [Sender.Caption]);

ShowMessageFmt ('Self = %s', [Self.Name]);

end;

procedure AssignButtonClick (const Button : TButton);

var Method : TMethod;

begin

Method.Code := @ButtonClick;

Method.Data := Button;

Button.OnClick := TNotifyEvent (Method);

end;

В этом случае в процедуре, предназначенной быть обработчиком события, необходимо явно описать еще один, дополнительный параметр, который и будет получать значение Self. Правильный метод его описания зависит от соглашения о связях (calling convention), используемого в процедурном типе и методе. Для модели register (которая используется для всех стандартных обработчиков и принята по умолчанию для всех процедур в Delphi) этот параметр должен идти первым, а за ним – все остальные, так, как они описаны в типе обработчика события. Для приведения типов используется специальный тип TMethod, описанный в модуле System; он представляет собой внутреннюю реализацию объектного процедурного типа.

В пользу такого подхода можно найти два аргумента. Во-первых, в этом случае мы легко можем передать в обработчик события любой параметр Self (так, в примере, мы передаем словно бы собственный метод кнопки). Во-вторых, зная действительные типы параметров, мы можем использовать их при декларации процедуры и избежать лишних приведений типов: так, в ButtonClick параметры Self и Sender определены как TButton, хотя, вообще говоря, корректней бы было описать их как TObject.

Операция @

Особым моментом, о котором необходимо упомянуть, является операция @ (взятие адреса). Вообще говоря, ее следовало бы использовать при всех операциях с процедурными типами. Так, процедуру Example из самого первого примера было бы в некотором смысле правильнее записать так:

procedure Example;

var CalcFunction : TCalcFunction;

begin

CalcFunction := @Add;

ShowMessageFmt('CalcFunction (2, 3) = %d', [CalcFunction (2, 3)]);

end;

Обратите внимание на использование операции @ в присваиваниях. Результат в этом случае будет абсолютно таким же: компилятор отслеживает операции с процедурными типами и при необходимости добавляет неявную операцию взятия адреса.

Мы могли бы написать:

if CalcFunction = Add then { ... }

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

if @CalcFunction = @Add then { ... }

Как правило, использования операции @ не требуется.

Применение ссылок на методы для расширения объектов

type

TTextReader = class

private

FOnReadLine: TReadLineEvent;

...

public

property OnReadLine: TReadLineEvent read FOnReadLine write FOnReadLine;

end;

Методы объектов, объявленные по приведенному выше шаблону, становятся совместимы по типу со свойством OnReadLine.

type

TForm1 = class(TForm)

procedure HandleLine(Reader: TTextReader; constLine: string);

end;

var

Form1: TForm1;

Reader: TTextReader;

Reader.OnReadLine := Form1.HandleLine;

Function TTextReader.NextLine: Boolean;

begin

if Assigned(FOnReadLine) then

FOnReadLine(Self, S); // уведомление о чтении очередной строки

end;

Объект Form1 через метод HandleLine получит уведомление об очередной считанной строке. Обратите внимание, что вызов метода через указатель происходит лишь в том случае, если указатель не равен nil. Эта проверка выполняется с помощью стандартной функции Assigned, которая возвращает True, если ее аргумент является связанным указателем.

Такой механизм называется делегированием, поскольку он позволяет передать часть работы другому объекту, например, сосредоточить в одном объекте обработку событий, возникающих в других объектах. Это избавляет программиста от необходимости порождать многочисленные классы-наследники и перекрывать в них виртуальные методы. Делегирование широко применяется в среде Delphi. Например, все компоненты делегируют обработку своих событий той форме, в которую они помещены.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]