我想根据它们是否触发它们的Handle程序
实现IEventHandler< TEventType>接口.
当发布传入事件时,我发现IEventHandler< TEventType>使用事件的类名和Spring4d的TType.FindType类型引用(‘IEventHandler< TEvent1>‘)
然后我遍历我的订阅者(实现IEventHandler接口的对象)并检查它是否支持IEventHandler类型.
问题是Supports方法返回true
即使订户没有实现该接口.
另外,我尝试列出了TMyEventHandler2类型的接口.
它包含IEventHandler< TEvent2> ??
我相信这是由于IEventHandler< TEvent2>的限制.
和IEventHandler< TEvent1>共享相同的GUID
这有解决方法吗?
使用这些课程&接口:
TEvent1 = class(TObject) end; TEvent2 = class(TObject) end; IEventHandler = interface(IInvokable) [guid] procedure Handle(aEvent : TObject); end; IEventHandler<T : class> = interface(IEventHandler) [guid] procedure Handle(aEvent : T); end; TMyEventHandler1 = class(TObject,IEventHandler,IEventHandler<TEvent1>) public procedure Handle(AEvent : TObject); overload; procedure Handle(AEvent : TEvent1); overload; end; TMyEventHandler2 = class(TObject,IEventHandler<TEvent2>) public procedure Handle(AEvent : TObject); overload; procedure Handle(AEvent : TEvent2); overload; end; TEventPublisher = class(TObject) public fSubscribers : IList<TValue>; procedure Subscribe(aSubscriber : TValue); // Simply adds the subscriber to the list of subscribers procedure Publish(aEvent : TObject); // Publishes an event to the subscribers end; procedure TEventPublisher.Publish(const event: TObject; ownsObject: Boolean = True); const IEventSubscriberName = 'IEventSubscriber<*>'; var consumerTypeName: string; consumerType : TRttiType; intfType : TRttiInterfaceType; subscriber : TValue; subscribed : IInterface; lEventSubscriber: IEventSubscriber; lIntfs : IReadOnlyList<TRttiInterfaceType>; begin consumerTypeName := StringReplace(IEventSubscriberName,'*',GetQualifiedClassName(event),[]); consumerType := TType.FindType(consumerTypeName); intfType := consumerType as TRttiInterfaceType; for subscriber in fSubscribers do begin lIntfs := TType.GetType(subscriber.AsObject.ClassInfo).GetInterfaces(); // lIntfs for TMyEventHandler2 containts IEventHandler<TEvent1> ??? if Supports(subscriber.AsObject,intfType.GUID,subscribed) then if Supports(subscriber.AsObject,IEventSubscriber,lEventSubscriber) then begin intfType.getmethod('Handle').Invoke(TValue.From(@subscribed,intfType.Handle),[event]) end; end; if ownsObject then event.Free; end; lEventPublisher := TEventPublisher.Create; lEventPublisher.Subscribe(TMyEventHandler1.Create); lEventPublisher.Subscribe(TMyEventHandler2.Create); lEventPublisher.Publish(TEvent1.Create); // Will both trigger TMyEventHandler1.Handle and TMyEventHandler2.Handle. Why ??
解决方法
我通常通过在接口中提供一种方法来解决这个问题(比如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<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,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方法正在使用一个小技巧,用IEventHandler< TObject>调用Support. – 因为eventSubscriber属于那种类型,我们可以将事件参数传递给它的Handle方法,这恰好是正确的 – 这是因为我之前解释过的二进制兼容性,因为我们只是处理不同的类作为T – story的类型如果我们没有那个类约束,那将完全不同.