Современные технологии программирования
Конспект лекций
назад | содержание | вперед

Лекция 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.

Словарь

 

Выводы

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

В ряде случаев бывает удобно использовать методы одного класса применительно к объектам другого класса. Это называется делегированием (метода). Для того, чтобы реализовать делегирование необходимо:

  1. описать тип указатель на метод для обработки адресов делегируемых методов;
  2. в классе, объектам которого вы хотите делегировать метод другого класса необходимо описать поле типа указатель на метод;
  3. в поле объекта типа указатель на метод занести значение адреса делегируемого метода;
  4. вызвать делегированный метод через поле указатель на метод.

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

 

Контрольные вопросы

  1. Как описать процедурный тип?
  2. Охарактеризуйте множество значений процедурного типа?
  3. Для чего используются переменные процедурного типа?
  4. Как записать значение процедурного типа в тексте программы?
  5. Чем отличается метод от процедуры
  6. Как описать тип указатель на метод?
  7. Охарактеризуйте множество значений типа указатель на метод?
  8. Для чего используются переменные типа указатель на метод?
  9. Как записать значение типа указатель на метод в тексте программы?
  10. Что такое делегирование?

 

Источники дополнительных сведений

 

 

 

наверх


назад | содержание | вперед