(2) //Свойства и события. Динамическое задание событий.
Q: Что такое Self?
Q: Что такое свойство (property)?
Q: Чем, на самом деле, является событие (event)?
Q: Почему компилятор ругается на попытку задать свои обработчики
событий для TApplication: Application.OnActivate := MyProcedure; ?
И опять-таки, начнем с определений. К сожалению, нередко среди
программистов можно встретить недопонимание того, что такое "метод".
Это понятие уже встречалось в ответе (1), и там оно использовалось в
общепринятом смысле: метод -- это функция или процедура, которую мы
описываем внутри класса. Все так, однако, при таком понимании вопроса
от нашего внимания ускользает важный аспект: чем отличается процедура,
описанная внутри класса (то есть, метод) и процедура описанная "просто
так", например, внутри секции Implementation?
Заметим, что машинный код любого метода размещается в памяти лишь
один раз, вне зависимости от того, сколько экземпляров данного класса
мы создали. Однако метод должен, как минимум, работать с конкретными
значениями полей, т.е. с конкретным объектом! А значит, метод должен
"уметь" как-то "узнавать" какой объект его вызвал. Раз так, то не
удивительно, что "внутрь" любого метода всегда, "неявно", передается
указатель на объект, который вызывает этот метод. Этот указатель носит
имя Self, который, быть может Вами уже когда-нибудь встречался.
Естественно, в обычную процедуру подобный указатель не передается! Вот
в этом и заключается важное отличие просто процедуры, и
процедуры-метода.
Это половина того, что нам необходимо знать для осознания того,
что такое Event. Вторая требуемая половина -- это понятие Property
(свойство). Как гласит старинный принцип ООП, все действия с полями
объекта должны осуществляться только через методы этого объекта.
Property -- практическое воплощение этого принципа. Стандартное
описание свойства (Property):
property MyValue:Integer read GetValue write PutValue;
определяет, что чтение целочисленной (Integer) величины MyValue будет
осуществляться через функцию
function GetValue:Integer;
а запись -- будет производится процедурой
procedure PutValue(Value:Integer);
Разумеется функция GetValue и процедура PutValue должны быть описаны
как методы того же класса. После подобного описания свойства MyValue,
команда MyObject.MyValue:=10; вызовет процедуру PutValue с параметром
10. Разумеется, переданное процедуре значение желательно сохранять,
поэтому стандартный вид процедуры PutValue:
procedure TMyObject.PutValue(Value:Integer);
begin FValue:=Value; {Любые дополнительные действия} end;
где FValue -- поле класса TMyObject, определенное, как правило в
private секции (чтобы быть недоступной "пользователю"). Аналогичные
действия происходят и при обращении ResultValue:=MyObject.MyValue.
Соответственно, функция GetValue описывается как
function TMyObject.GetValue:Integer;
begin Result:=FValue; {Любые дополнительные действия} end;
В итоге, мы добились упомянутого выше принципа: вся работа с полем
FValue происходит через методы нашего класса (более того,
"пользователь" даже не догадывается о существовании поля FValue, он
работает только со свойством Value). Таким образом, принцип-принципом,
но такой подход позволяет скрыть от других людей методы и поля,
представляющие собой внутреннее устройство класса. К тому же, это дает
и немалую практическую пользу, например, если значение свойства связано
с какими-то другими характеристиками класса (объекта), которые должны
меняться при изменении самого свойства (примером может служить свойства
left и top большинства (если не всех) контролов, изменение значений
которых автоматически влечет изменения положения контрола).
Если же чтение или запись должны осуществляться прямо из/в какого-
то поля без каких-либо "дополнительных действий", то допускается такое
определение свойства:
property MyValue:Integer read FValue write FValue;
Итак, что же такое event? Hа самом деле, не более, чем свойство
процедурного типа:
private
FOnChange: TNotifyEvent;
public
property OnChange:TNotifyEvent read FOnChange write FOnChange;
где TNotifyEvent это процедурный тип. А именно
TNotifyEvent = procedure(Sender: TObject) of object;
Вроде бы все должно быть ясно, за исключением одного обстоятельства:
что означает "приписка" of object? Для этого вспомним начало нашего
обсуждения: раз эта процедура будет работать с конкретным объектом, то
"внутрь" нее должен передаваться указатель на объект, тот самый Self.
Так вот, of object объявляет новый процедурный тип, как тип таких
процедур, в которых передается указатель на объект. Именно поэтому
недопустимо присваивание вроде Application.OnActivate:=MyProcedure,
если MyProcedure не описана как метод какого-нибудь класса (компилятор
будет выдавать ошибку из-за несоответствия типа присваиваемой
процедуры). Более того, на момент присваивания объект того класса, в
котором описана MyProcedure должен быть уже создан! Иначе попытка
присваивания приведет к AV.
Как правило, (если речь идет о переопределении событий у
Application) такую процедуру описывают внутри главной формы проекта, а
само присваивание производят внутри обработчика события OnCreate этой
формы. Хорошей альтернативой служит такой вариант задания собственного
обработчика события для Application, прямо внутри dpr-файла:
type TActivateEvent = class
public
procedure MyActivate(Sender:TObject);
end;
begin
with TActivateEvent.Create do
begin
Application.OnActivate:=MyActivate;
Application.Initialize;
//..... создание форм
Application.Run;
Free
end
end;
[ Назад ]
|