![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Рассылка событий группе объектов. [D7, WinXP]
GuAV © (2005-11-23 14:09:00)Здравсвуйте.
Мне нужно вызывать из одного объекта методы (обработчики событий) нескольких других объектов, причём вызывающий заранее не знает каким объектам, но целевой класс "знает", какие события ему нужны (и предоставляет свои методы).
В данный момент используется следующее решение:
type
TOnCaptionChanged = procedure(Sender: TObject; const Old, New: string) of object;
TOnObjectChanged = procedure(Sender: TMapPolygon) of object;
...
type
TObjChangeNotifier = class(TObject)
private
FCaptionChanged, FObjectChanged, ... : TList;
procedure RegisterHandler(List: TList; const Handler: TMethod; Register: Boolean);
procedure ClearList(List: TList);
public
constructor Create;
destructor Destroy; override;
procedure CaptionChanged(Sender: TObject; const Old, New: string);
procedure ObjectChanged(Sender: TMapPolygon);
...
procedure RegisterOnCaptionChanged(const Handler: TOnCaptionChanged; Register: Boolean);
procedure RegisterOnObjectChanged(const Handler: TOnObjectChanged; Register: Boolean);
...
end;
constructor TObjChangeNotifier.Create;
begin
inherited;
FCaptionChanged := TList.Create;
FObjectChanged := TList.Create;
...
end;
destructor TObjChangeNotifier.Destroy;
begin
ClearList(FCaptionChanged);
ClearList(FObjectChanged);
...
FreeAndNil(FCaptionChanged);
FreeAndNil(FObjectChanged);
...
inherited;
end;
procedure TObjChangeNotifier.RegisterHandler(List: TList;
const Handler: TMethod; Register: Boolean);
var
M: PMethod;
I: Integer;
begin
with List do
if Register then
begin
New(M);
M^ := Handler;
Add(M);
end
else
begin
for I := Count - 1 downto 0 do
begin
M := PMethod(Items[I]);
with M^ do
if (Code = Handler.Code) and (Data = Handler.Data) then
begin
Delete(I);
Dispose(M);
end;
end;
end
end;
procedure TObjChangeNotifier.CaptionChanged(Sender: TObject; const Old,
New: string);
var
I: Integer;
begin
with FCaptionChanged do
for I := 0 to Count - 1 do
TOnCaptionChanged(PMethod(Items[I])^)(Sender, Old, New);
end;
procedure TObjChangeNotifier.ObjectChanged(Sender: TMapPolygon);
var
I: Integer;
begin
with FObjectChanged do
for I := 0 to Count - 1 do
TOnObjectChanged(PMethod(Items[I])^)(Sender);
end;
....
procedure TObjChangeNotifier.RegisterOnCaptionChanged(
const Handler: TOnCaptionChanged; Register: Boolean);
begin
RegisterHandler(FCaptionChanged, TMethod(Handler), Register);
end;
procedure TObjChangeNotifier.RegisterOnObjectChanged(
const Handler: TOnObjectChanged; Register: Boolean);
begin
RegisterHandler(FObjectChanged, TMethod(Handler), Register);
end;
...
в AfterConstruction целевого класса
ObjChangeNotifier.RegisterOnCaptionChanged(CaptionChanged, True);
в BeforeDestruction целевого класса
ObjChangeNotifier.RegisterOnCaptionChanged(CaptionChanged, False);
Вызов события
ObjChangeNotifier.ObjectChanged(CurrentObject);
Однако, очевидно, что для большого числа методов такое решение не является удобным. Есть идея перейти на использование сообщений delphi, тогда TObjChangeNotifier будет содержать только один список (или он сам и будет наседником списка) и один метод диспетчеризации (возможно даже его же TObject.Dispatch), при этом элементами будут не методы а сами объекты (избавление от New и Dsipose). Какие возможны проблемы при этом и как это лучше реализовать ?
Методы должны вызываться синхронно. Порядок роли не играет. Число параметров различных методов различно, переменных параметров нет. Производительность (быстродейтсвие) является фактором, однако удобство главнее.
Хотелось бы услышать ваше мнение как удобнее реализовать рассылку событий группе объектов - через сообщения Delphi, сообщения Windows или другим способом - и как именно. Вообще, как Вы реализуете (или реализовали бы) сабж в своих проектах.
GuAV (2005-11-23 17:30:00) [41]
> > На паттерн Visitor не хочешь посмотреть ?
Насколько я понял он позволяет добавить метод при фиксированной иерархии классов. У меня никакая не иерархия, просто куча разных классов, и, что является проблемой, рост их числа и рост числа методов одинаково ожидается.
GuAV (2005-11-23 17:15:00) [42]
> Я бы использовал именно Enumeration, чтобы возможные
> ошибки (опечатки, например) были выловлены на стадии
> компиляции.
Согласен, однако целочисленные константы с осмысленными идентификаторами снижают риск опечатки так же хорошо :)
При этом, можно задать тип поддиапазона TObjMessage = OM_FIRST..OM_LAST, и использовать его.
> Я не совсем понимаю, так как в случае с Variant
> приведение типов контролируется также во время
> выполнения, а в случае с const или var параметрами
> приведение типов остается на совести программиста.
Ошибки типа "вызванно не то событие" в любом случае недопустимы и не контролируются. И в обоих случаях обработчик (молча или с исключением, но) проглотит не его данные (ещё неизвестно что хуже, получить там AV, или привести catption равный 2 к double, второе ловить сложнее).
Ошибки типа не тот параметр не на том месте (и в обработчике и в вызывающем) легче исключить именно в случае с записью.
И потом, Sender: TObject, Sender: TMapPolygon, Sender: TList - это классы, как их в вариант пихать ?
> На паттерн Visitor не хочешь посмотреть ?
Пошел искать. Спасибо.
Игорь Шевченко (2005-11-23 16:57:00) [43]GuAV © (23.11.05 16:46) [3]
> Если завести Enumeration для типа событий, то можно указывать
> его в методе-"регистраторе". Вы это имеете ввиду?
Например. Или просто константу. Я не совсем понимаю, какой смысл использовать сообщения (Windows или Delphi, не слишком большая разница).
Я бы использовал именно Enumeration, чтобы возможные ошибки (опечатки, например) были выловлены на стадии компиляции.
> Что касается заполнения - в варианте с const или var параметром
> в классе-генераторе события объявляется нужная запись и
> заполняется - это более удобно, так как есть типизация,
> в отличие от Variant.
Я не совсем понимаю, так как в случае с Variant приведение типов контролируется также во время выполнения, а в случае с const или var параметрами приведение типов остается на совести программиста.
> Но сущесвенный недостаток: необходимость добавления пустого
> метода в каждый класс при появлении нового события.
На паттерн Visitor не хочешь посмотреть ?
GuAV (2005-11-23 16:46:00) [44]Игорь Шевченко © (23.11.05 15:25) [1]
> указывать еще и тип события, на который обработчик
> должен реагировать
Как указывать ? В настоящем варианте просто используются разные методы для регистрации различных событий.
Если завести Enumeration для типа событий, то можно указывать его в методе-"регистраторе". Вы это имеете ввиду?
Если же делать сообщения или нечто "сообщенияподобное", то необходимость указания типа обработчика отпадает - класс безусловно готов принять все события, а нужны ли они ему - он сам решит.
При использовании именно message-методов обработчика может не существовать - однако меня интересует, какая реакция будет при этом. Существуют контролы (фреймы) с созданным или несозданным окном, а также прямые наследники TObject, и хотелось бы обеспечить отсутствие реакции на необрабатываемое во всех трёх случаях. Возможно ли
это, и если да то как ?
Игорь Шевченко © (23.11.05 15:25) [1]
> а параметры передавать одним Variant (в который можно
> поместить массив других вариантов).
Рассмотрю как вариант. Однако, как мне кажется удобнее принимать один нетипизированый const или var параметр (в обработчике привести к нужному Record), а если таки ипользовать сообщения Delphi, то это - "естественный" путь.
Что касается заполнения - в варианте с const или var параметром в классе-генераторе события объявляется нужная запись и заполняется - это более удобно, так как есть типизация, в отличие от Variant.
jack128 © (23.11.05 15:41) [2]
> Я делаю так:
Я понял. Идея ApplicationEvents. смотрел. Особенно хорошо для визуального применения, если TSomeObjEvents - компонент и зарегистрирован (этот вариант тоже рассматривал).
>>тогда TObjChangeNotifier будет содержать только один список
> А какая разница один или несколько?
Обрати внимание на код [0]. Каждый метод-диспетчер сканит свой список, в комструкторе и деструкторе все списки упоминаются по разу - неудобно, главная причина отказа.
>>и один метод диспетчеризации
> Ничего хорошего в этом не вижу
А какая разница один или несколько? © jack128
Буду ли я указывать индексом события или именем метода - ИМХО безразлично.
Зато один метод - и цикл писать один.
Теряется типизация, но её можно вернуть пачкой методов, в них запускать главный с соотв. индексом.
Кстати, при использовании индексных свойств заместо методов, можно из одного сделать несколько. Но это уже типизацию не восстановит.
>>(избавление от New и Dsipose).
> А в чем проблема?
Да, не так чтобы сильно, но лишнее выделение/освобождение и лишний уровень косвенности. Мелочь, но неприятно.
> Я обычно здесь делаю обратный цикл, чтобы дать
> возможность подписчику отписаться в самом обработчике
> события.
Спасибо. Но мне пока не требуется - подписка или до BeforeDestruction или вообще пожизненная (до Destroy).
Ещё была идея: интерфейс, реализованный диспетчером и классами. Преимущество - максимальная типизация.
Но сущесвенный недостаток: необходимость добавления пустого метода в каждый класс при появлении нового события.
Склоняюсь к варианту:
Сообщения, для каждого события своё.
События:
TOnCaptionChanged = record
Message: LongWord; { = WM_USER + 3}
Sender: TObject;
Old, New: string;
end;
TOnObjectChanged = record
Message: LongWord; { = WM_USER + 4}
Sender: TMapPolygon;
end;
...
Главный метод диспетчеризации - TObject.Dispatch диспечера, вспомогательные по именам событий для типизации входного recordа, заполняют Message и вызывают главный. Жаль, что строки и варианты не допускаются в переменной части записи.
jack128 (2005-11-23 15:41:00) [45]Я делаю так:
TSomeObjEvents = class
public
property Obj: TObjChangeNotifier read write;
property OnCaptionChanged: TOnCaptionChanged read write;
property OnObjectChanged: TOnObjectChanged read write;
end;
Подписчики создают объект TSomeObjEvents и задают свойство Obj. А Obj храниться список соответствующих TSomeObjEvents
GuAV © (23.11.05 14:09)
тогда TObjChangeNotifier будет содержать только один список
А какая разница один или несколько?
GuAV © (23.11.05 14:09)
и один метод диспетчеризации
Ничего хорошего в этом не вижу
GuAV © (23.11.05 14:09)
(избавление от New и Dsipose).
А в чем проблема?
ЗЫ
GuAV © (23.11.05 14:09)
for I := 0 to Count - 1 do
TOnObjectChanged(PMethod(Items[I])^)(Sender);
Я обычно здесь делаю обратный цикл, чтобы дать возможность подписчику отписаться в самом обработчике события.
Игорь Шевченко (2005-11-23 15:25:00) [46]Я бы предложил при регистрации обработчика события указывать еще и тип события, на который обработчик должен реагировать, а параметры передавать одним Variant (в который можно поместить массив других вариантов). Тогда один список, разные обработчики знают, какой список параметров соотвествует их событию и т.д.