发生这种情况的原因是,如果在泛型接口上放置guid,则该接口的每个专门化都将具有相同的guid,而不管其泛型类型参数如何。
我通常通过在界面中提供相关信息来解决这个问题(如
Spring.Collections.IEnumerable
具有
ElementType
属性来获取
IEnumerable<T>
).
因此,实现如下所示:
program GenericEventPublisher;
{$APPTYPE CONSOLE}
uses
Spring,
Spring.Collections,
System.SysUtils;
type
IEventHandler = interface
['{2E4BD8F4-4EB8-4B33-84F4-B70F42EF9208}']
procedure Handle(const event: TObject);
end;
IEventHandler<T: class> = interface
['{82B7521E-D719-4051-BE2C-2EC449A92B22}']
procedure Handle(const event: T);
function GetHandledClass: TClass;
end;
IEventPublisher = interface
['{2A460EF0-AE27-480F-ACEA-1B897F2DE056}']
procedure Subscribe(const subscriber: IEventHandler);
procedure Publish(const event: TObject; ownsObject: Boolean = True);
end;
TEventHandlerBase<T: class> = class(TInterfacedObject, IEventHandler, IEventHandler<T>)
private
function GetHandledClass: TClass;
procedure Handle(const event: TObject); overload;
public
procedure Handle(const event: T); overload; virtual; abstract;
end;
TEvent1 = class
end;
TEvent2 = class
end;
TMyEventHandler1 = class(TEventHandlerBase<TEvent1>)
public
procedure Handle(const event: TEvent1); override;
end;
TMyEventHandler2 = class(TEventHandlerBase<TEvent2>)
public
procedure Handle(const event: TEvent2); override;
end;
TEventPublisher = class(TInterfacedObject, IEventPublisher)
private
fSubscribers: IList<IEventHandler>;
public
constructor Create;
procedure Subscribe(const subscriber: IEventHandler);
procedure Publish(const event: TObject; ownsObject: Boolean = True);
end;
{ TEventPublisher }
constructor TEventPublisher.Create;
begin
fSubscribers := TCollections.CreateList<IEventHandler>;
end;
procedure TEventPublisher.Publish(const event: TObject; ownsObject: Boolean);
var
subscriber: IEventHandler;
eventSubscriber: IEventHandler<TObject>;
begin
for subscriber in fSubscribers do
if Supports(subscriber, IEventHandler<TObject>, eventSubscriber)
and (eventSubscriber.GetHandledClass = event.ClassType) then
eventSubscriber.Handle(event);
if ownsObject then
event.Free;
end;
procedure TEventPublisher.Subscribe(const subscriber: IEventHandler);
begin
fSubscribers.Add(subscriber)
end;
{ TEventHandlerBase<T> }
function TEventHandlerBase<T>.GetHandledClass: TClass;
begin
Result := T;
end;
procedure TEventHandlerBase<T>.Handle(const event: TObject);
begin
Assert(event is T);
Handle(T(event));
end;
{ TMyEventHandler1 }
procedure TMyEventHandler1.Handle(const event: TEvent1);
begin
Writeln(event.ClassName, ' handled by ', ClassName);
end;
{ TMyEventHandler2 }
procedure TMyEventHandler2.Handle(const event: TEvent2);
begin
Writeln(event.ClassName, ' handled by ', ClassName);
end;
var
eventPublisher: IEventPublisher;
begin
eventPublisher := TEventPublisher.Create;
eventPublisher.Subscribe(TMyEventHandler1.Create);
eventPublisher.Subscribe(TMyEventHandler2.Create);
eventPublisher.Publish(TEvent1.Create);
eventPublisher.Publish(TEvent2.Create);
end.
由于接口上有类约束,我们可以确保接口是二进制兼容的,无论T的类型如何(因为它们只能是对象)。同时,为通用事件处理程序使用基类型可以减少编写的额外代码。它只是重定向非泛型
Handle
方法转换为必须在具体实现中实现的通用方法。
此外,由于基类实现了这两个接口,我们不需要将处理程序存储在
TValue
但是可以使用非通用接口类型,并且无需RTTI即可轻松访问它们。
现在
Publish
方法正在使用小技巧调用
Support
具有
IEventHandler<TObject>
-自
eventSubscriber
是那种我们可以通过
event
its的参数
手柄
方法恰好是正确的,这是因为我之前解释过的二进制兼容性,因为我们只是处理不同的类,因为如果没有类约束,T-story的类型将完全不同。