Современные технологии программирования |
Конспект лекций |
Лекция 12. Делегирование
Содержание
Источники дополнительных сведений *
Когда пользователь каким-либо образом воздействует на компонент, например, щелкает на нём, компонент генерирует событие. Другие события порождаются системой в ответ на вызов метода или в ответ на изменение одного из свойств (данного или даже другого) компонента. Например, если вы установите фокус ввода на какой-либо компонент, то компонент, на котором фокус ввода был раньше, теряет его, возбуждая соответствующее событие.
Формально большинство событий Delphi инициируются при получении соответствующего сообщения Windows, хотя события и не образуют взаимно однозначного соответствия с этими сообщениями. События Delphi представляют более высокий уровень, чем сообщения Windows, кроме того, Delphi генерирует ряд межкомпонентных сообщений.
С теоретической точки зрения, событие есть результат сообщения, посланного окну, при этом окно (или соответствующий компонент) может ответить на сообщение. Следуя этому подходу, для обработки события щелчка на кнопке нам необходимо было бы создать подкласс класса TButton и добавить в него новый обработчик события.
На практике, однако, создавать новый класс нерационально, поскольку это слишком сложная операция. Обычно в Delphi обработчик события компонента – это метод формы, содержащей этот компонент, а не самого компонента. Такая технология называется делегированием, и она является основой компонентной модели Delphi.
Для того чтобы можно было обрабатывать в программе подпрограммы как данные, в состав языка Delphi ввёдены типы, которые носят название процедурные типы.
Процедурные типы описываются в разделе описания типов.
Множество значений процедурного типа – это множество указателей (адресов) подпрограмм, заголовки которых совпадают с заголовком, представленным в описании процедурного типа. Например:
type
Proc = procedure;//значениями типа являются адреса процедур без параметров
DeviceFunc = function (var f: text): integer;//значениями типа являются адреса функций,
//с параметром-переменной типа text, возвращающих значение типа integer
StrProc = procedure (s: string);//значениями типа являются адреса процедур
//с параметром-значением типа string
IntProc = procedure (N: integer);//значениями типа являются адреса процедур
//с параметром-значением типа integer
MuthFunc = function (x: real): real;//значениями типа являются адреса функций,
//с параметром-значением типа real, возвращающих значение типа real
MaxFunc = function (a, b: real; F: MuthFunc): real;//значениями типа являются адреса
//функций, с двумя параметрами-значениями типа real, с
//параметром-значением процедурного типа MuthFunc, возвращающих
//значение типа real
Значение процедурного типа в тексте программы представляет идентификатор подпрограммы с соответствующим типу заголовком.
Процедурные типы можно использовать для:
Параметры процедурного типа можно использовать для передачи в подпрограмму при вызове других подпрограмм.
Переменные процедурного типа можно использовать для вызова подпрограмм, как показано в примере ниже.
Пример 1. Процедурные типы
program PExmple1;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
//------------------------------------------------Процедурные типы-----------
SwapProc = procedure (var x, y: integer);
MuthFunc = function (x: real): real;
var
//-----------------------------------Переменные процедурных типов------------
P: SwapProc;
F: MuthFunc;
//---------------------------------------------------------------------------
i, j: integer;
V: Pointer;
//---------------------------------------------------------------------------
procedure Swap(var a, b:integer);
//Обмен переменных значениями
var Temp:integer;
begin
Temp:= a; a:= b; b:= Temp;
end;
//---------------------------------------------------------------------------
function Tg(Angle: real): real;
//Вычисление тангенса угла
begin
result:= Sin(Angle)/Cos(Angle);
end;
//---------------------------------------------------------------------------
function Sqr(x: real): real;
//Квадрат числа
begin
result:= x*x;
end;
//---------------------------------------------------------------------------
procedure Print(x: real; f: MuthFunc);
//Вычисление тангенса угла
begin
writeln(f(x):6:2);
end;
//---------------------------------------------------------------------------
begin
i:=10;j:=20;
P:= Swap; //Переменной P присваивается указатель на процедуру Swap
P(i,j); //Вызов процедуры Swap через переменную P
writeln('i = ',i,' j = ',j);
F:= Tg; //Переменной F присваивается указатель на функцию Tg
writeln('tg(pi/4) = ',F(pi/4):6); //Вызов функции Tg через переменную F
V:= @Swap; //Переменной P присваивается указатель на процедуру Swap
SwapProc(V)(i,j); //Переменная V приводится к типу SwapProc
// и вызывается процедура Swap
writeln('i = ',i,' j = ',j);
V:= @Tg; //Переменной V присваивается указатель на функцию Tg
writeln('tg(pi/4) = ',MuthFunc(V)(pi/4):6); //Переменная V приводится к
//типу MuthFunc и вызывается функция Tg
Print(pi/4,Tg);
F:= Sqr; //Переменной F присваивается указатель на функцию Tg
Print(4,Sqr);
readln;
end.
Для того чтобы можно было обрабатывать в программе методы классов как данные, в состав языка Delphi ввёдены типы, которые носят название указатели на методы.
Тип указатель на метод описываются в разделе описания типов. Например:
type
TNotifyEvent = procedure (sender: TObject) of object;//значениями типа являются адреса
//методов, представленных процедурами с одним параметром-значением типа TObject
TMouseEvent = procedure (Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer) of object;
TMouseButton = (mbLeft, mbRight, mbMiddle);
TShiftState = set of (ssShift, ssAlt, ssCtrl, ssLeft, ssRight, ssMiddle, ssDouble);
При описании указатель на метод отличается от процедурного типа наличием зарезервированными словами “of object”. Отличие в описании вызвано, отличием при вызове. Так при вызове метода в него всегда передаётся параметр по умолчанию (self) – указатель на объект, вызвавший метод.
Множество значений типа указатель на метод – это множество указателей (адресов) методов класса, заголовки которых совпадает с заголовком, представленным в описании типа указатель на метод.
Значение типа указатель на метод в тексте программы представляет идентификатор метода с соответствующим заголовком.
Типы указателей на метод можно использовать для:
Переменная типа указатель на метод используется для вызова метода, на который в данный момент времени переменная ссылается.
Поля типа указатель на метод позволяют делегировать метод одного класса объекту другого класса, чтобы в дальнейшем можно было применять этот метод к объекту. На уровне текста программы полю объекта типа указатель на метод присваивается адрес соответствующего метода.
Пример работы с указателями на методы приведён ниже.
Пример 2. Указатели на методы
unit Unit1;
interface
type
TPtrMethod = function: real of object; //тип указатель на метод
T1 = class
function Get: real;
end;
T2 = class
FPtrM: TPtrMethod; //Поле типа указатель на метод
end;
implementation
function T1.Get: real;
begin
result:= 0;
end;
end.
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils,
Unit1 in 'Unit1.pas';
var O1: T1; O2: T2;
begin
O1:= T1.Create;
O2:= T2.Create;
O2.FPtrM:= O1.Get; //Делегирование метода Get класса T1 объекту O2 класса T2
writeln(O2.FPtrM); //Вызов метода Get класса T1 объектом O2 класса T2
readln;
end.
Сообщение Windows – это сигнал о наступлении события. Сообщений в Windows сотни, написать программу для Windows – значит, назначить и описать реакцию на них.
Работать с таким числом сообщений, даже имея под рукой справочник, нелегко. Поэтому, одним из больших достижений Delphi является то, что программист избавлен от необходимости работать с большим числом сообщений Windows (хотя такая возможность у него и есть). Стандартных событий в Delphi не более двух десятков, и все они имеют простую интерпретацию, не требующую глубоких знаний среды.
Рассмотрим, как реализованы эти события в Delphi.
События – это свойства типа указатель на метод, предназначенные для создания пользовательской реакции на те или иные входные воздействия:
Property OnMyEvent: TMyEvent read FOnMyEvent write FOnMyEvent;
Присвоить такому свойству значение – это значит, указать объекту адрес метода, который будет вызываться в момент наступления события. Такие методы называются обработчиками событий. Например, когда вы пишите
Application.OnActivate:= MyActivatingMethod;
Это значит, что при каждой активизации Application (так называется объект, соответствующий работающему приложению) будет вызван метод-обработчик MyActivatingMethod.
События имеют разные типы в зависимости от происхождения и предназначения. Общим для всех является параметр Sender – он указывает на объект – источник события. Самый постой тип – TnotifyEvent не имеет других параметров
type
TNotifyEvent = procedure (sender: TObject) of object;
Все события в Delphi принято именовать с “On”: OnCreate, OnMouseMove, OnPaint и т.д. Щелкнув в инспекторе объектов на странице Events в поле любого события, вы получите в программе заготовку метода нужного типа. При этом его имя будет состоять из имени текущего компонента и имени события без On, а относиться он будет к текущей форме. Пусть, например, на форме Form1 есть метка Label1. Тогда для обработки щелчка мышью на нём (событие OnClick) будет создан метод TForm1.Label1Click
Поскольку события – это свойства объекта, их значения можно изменять во время выполнения программы. Такая возможность называется делегированием. Вы можете в любой момент времени взять способ реакции на событие у одного объекта и присвоить делегировать его другому
Object1.OnMouseMove:= Object2.OnMouseMove;
Принцип делегирования позволяет избежать трудоёмкого процесса порождения новых дочерних классов для каждого специфического случая, заменяя его простой подстановкой методов.
Пример 3. Делегирования методов
unit UMethRef;
interface
type
//-------------------------------------------------------------------------------------------------------
TMyFunc = function (X: Real): Real of object;//тип указатель на метод
//-------------------------------------------------------------------------------------------------------
TObj = class(TObject)
private
FField: Real;
FMyFunc: TMyFunc; //поле типа указатель на метод
function GetF: Real;
function FirstFunc(X: Real): Real;
public
property Field: Real read GetF write FField;
property Func: TMyFunc read FMyFunc write FMyFunc; //свойство типа указатель на метод
constructor Create(new: Real);virtual;
end;
//-------------------------------------------------------------------------------------------------------
TObj1 = class
function SecondFunc(X: Real):Real;
end;
implementation
//-------------------------------------------------------------------------------------------------------
constructor TObj.Create;
begin
FField:= new;
Func:= FirstFunc;
end;
//-------------------------------------------------------------------------------------------------------
function TObj.FirstFunc;
begin
Result:= Sqr(X)/2;
end;
//-------------------------------------------------------------------------------------------------------
function TObj.GetF: Real;
begin
Result:= Func(FField);
end;
//-------------------------------------------------------------------------------------------------------
function TObj1.SecondFunc(X: Real): Real;
begin
Result:= Sqrt(X)*2;
end;
end.
program PMethRef;
uses
Forms,
UMethRef in 'UMethRef.pas';
{$R *.RES}
var
O: TObj;
O1: Tobj1;
begin
O:= TObj.Create(4);
writeln(O.Field:8:3);//8
O.Field:= 9;//Изменяем значение поля
writeln(O.Field:8:3);//40,5 работает метод FirstFunc
O1:= TObj1.Create;
O.Func:= O1.SecondFunc;//делегируем метод SecondFunc
writeln(O.Field:8:3);//6 работает метод SecondFunc
readln;
O.Free;
O1.Free;
end.
Пример 4. Делегирование обработчиков событий
Приложение, форма которого приведена ниже, отображает в заголовке формы имя компонента, над которым находится указатель мыши. Это достигается следующим. Метод обработчик события Button1.OnMouseMove помещает в свойство Caption формы имя компонента, ставшего источником события OnMouseMove. Этот обработчик делегируется объектам Form1 и Button1при создании формы.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
procedure FormCreate(Sender: TObject);//Обработчик события создание формы
procedure Button1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);//обработчик события указатель мыши над кнопкой
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
Memo1.OnMouseMove:= Button1.OnMouseMove;//делегирование
OnMouseMove:= Button1.OnMouseMove; //делегирование
end;
procedure TForm1.Button1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
Caption:= (Sender as TControl).Name;//отображается имя кнопки в заголовке формы,
//когда указатель мыши расположен над кнопкой
end;
end.
Мы рассмотрели с вами процедурные типы, которые позволяют обрабатывать в программе адреса процедур как данные. С появлением классов процедурные типы расширены типом указатель на метод, который позволяет обрабатывать адреса методов класса как данные. Несмотря на то, что для реализации методов класса используются подпрограммы, между ними существует отличие. Это отличие состоит в том, что при вызове метода в него по умолчанию передаётся указатель на объект, который его вызвал.
В ряде случаев бывает удобно использовать методы одного класса применительно к объектам другого класса. Это называется делегированием (метода). Для того, чтобы реализовать делегирование необходимо:
Делегирование наиболее целесообразно использовать для методов обработчиков событий визуальных компонентов, при разработке интерфейсов. Хотя допустимо делегировать любые методы одного класса объектам другого.
Источники дополнительных сведений