代码之家  ›  专栏  ›  技术社区  ›  Ludovic C

Delphi SysUtils。支持意外返回true

  •  4
  • Ludovic C  · 技术社区  · 7 年前

    我正在基于Spring4d的文档示例制作eventPublisher

    不同之处在于订阅者必须明确订阅事件。

    我想根据他们是否 实施 IEventHandler<TEventType> 界面

    发布传入事件时,我发现 伊文坦德勒<TEventType> 使用事件的类名和Spring4d的 TType.FindType('IEventHandler<TEvent1>')

    然后我循环遍历我的订阅者(实现IEventHandler接口的对象),并检查它是否支持IEventHandler类型,例如。

    问题是Supports方法返回true 即使订阅者没有实现该接口。

    此外,我尝试列出say的接口 TMyEventHandler2 类型 它包含 IEventHandler<TEvent2> ??

    我相信这是由于 伊文坦德勒<TEvent2> IEventHandler<TEvent1> 共享相同的GUID

    是否有解决方法?

    使用这些类(&A);接口:

    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, 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 ??
    
    1 回复  |  直到 7 年前
        1
  •  6
  •   Stefan Glienke    7 年前

    发生这种情况的原因是,如果在泛型接口上放置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的类型将完全不同。