【问题标题】:How to test the type of a generic interface?如何测试泛型接口的类型?
【发布时间】:2010-12-20 19:17:22
【问题描述】:

我不确定这个标题是否有意义,但我希望你能用一些代码理解我的问题。

给定以下发布/订阅框架的代码。

type
  IMessage = interface
    ['{B1794F44-F6EE-4E7B-849A-995F05897E1C}']
  end;

  ISubscriber = interface
    ['{D655967E-90C6-4613-92C5-1E5B53619EE0}']
  end;

  ISubscriberOf<T: IMessage> = interface(ISubscriber)
    procedure Consume(const message: T);
  end;

  TMessageService = class
  private
    FSubscribers: TList<ISubscriber>;
  public
    constructor Create;
    destructor Destroy; override;
    procedure SendMessage(const message: IMessage);
    procedure Subscribe(const Subscriber: ISubscriber);
    procedure Unsubscribe(const Subscriber: ISubscriber);
  end;

会这样使用:

TMyMessage = class(TInterfacedObject, IMessage);

TMySubscriber = class(TInterfacedObject, ISubscriberOf<TMyMessage>)
  procedure Consume(const Message: TMyMessage);
end;

TMyOtherMessage = class(TInterfacedObject, IMessage);

TMyOtherSubscriber = class(TInterfacedObject, ISubscriberOf<TMyOtherMessage>)
  procedure Consume(const Message: TOtherMessage);
end;

如何循环订阅者列表并将消息发送给正确的订阅者?

订阅者列表将包含所有类型消息的所有订阅者。 SendMessage 必须找到作为参数提供的消息类型的订阅者,并将其发送给实现适当接口以使用该类型消息的订阅者。

  procedure TMessageService.SendMessage(const message: IMessage);
  var
    Subscriber: ISubscriber;
  begin
    for Subscriber in FSubscribers do
    begin
      // How to send the message only to the subscribers of the correspondent type of message
    end;
  end;

谢谢!

顺便说一句,此代码基于此blog post

【问题讨论】:

  • 很难知道您的问题是什么以及您所说的“正确的订阅者”是什么意思。很难找到阅读您链接到的博客文章的动机。如果您要以尽可能简洁的方式进行总结,那么您可能会得到更多帮助。
  • 感谢@David,我添加了更多信息以尝试解释更多我想要的内容。
  • TMessageService.SendMessage将消息发送给每个订阅者,让订阅者的Consume方法决定是处理还是忽略它不是更容易吗?
  • @codeelegance 可能更容易,但恕我直言,这是一个糟糕的实现。如果同一个对象订阅多条消息,那将是一个很大的 if 或 case 语句。

标签: delphi generics


【解决方案1】:

编辑:找到了一种减少复杂性的方法(请按您喜欢的方式对这个答案投票;花了很长时间才弄对)。
请注意,它使用新的 Rtti 单元,因此它适用于 Delphi 2010 及更高版本(我使用 Delphi XE 进行开发,我尚未在 Delphi 2010 中验证这一点)。

对于Supports,您需要存储一些IID GUID 与您的界面和查询它们的方法。
由于您想将此与泛型一起使用,因此您希望能够从接口类型而不是从接口引用(如Hallvard Vassbotn showed with a hack in 2006)查询 IID GUID。
Delphi 2010 中引入的新 RTTI 允许您这样做:

unit RttiUnit;

interface

type
  TRtti = record
    //1 similar to http://hallvards.blogspot.com/2006/09/hack11-get-guid-of-interface-reference.html but for the interface type, not for a reference
    class function GetInterfaceIID<T: IInterface>(var IID: TGUID): Boolean; static;
  end;

implementation

uses
  TypInfo,
  Rtti;

class function TRtti.GetInterfaceIID<T>(var IID: TGUID): Boolean;
var
  TypeInfoOfT: PTypeInfo;
  RttiContext: TRttiContext;
  RttiInterfaceType: TRttiInterfaceType;
  RttiType: TRttiType;
begin
  TypeInfoOfT := TypeInfo(T);
  RttiContext := TRttiContext.Create();

  RttiType := RttiContext.GetType(TypeInfoOfT);
  if RttiType is TRttiInterfaceType then
  begin
    RttiInterfaceType := RttiType as TRttiInterfaceType;
    IID := RttiInterfaceType.GUID;
    Result := True;
  end
  else
    Result := False;
end;

end.

所以现在更改的代码,我重新排列了一下,并分布在更多单元上以保持概览。

ClassicMessageSubscriberUnit:具有非通用接口IMessageISubscriber(它们源自IImplementedWithClass,这使得记录事情变得更容易。

unit ClassicMessageSubscriberUnit;

interface

type
  IImplementedWithClass = interface(IInterface)
    function ToString: string;
  end;

  IMessage = interface(IImplementedWithClass)
    ['{B1794F44-F6EE-4E7B-849A-995F05897E1C}']
  end;

  ISubscriber = interface(IImplementedWithClass)
    ['{D655967E-90C6-4613-92C5-1E5B53619EE0}']
  end;

implementation

end.

GenericSubscriberOfUnit:包含泛型ISubscriberOf 接口,它继承自泛型ISupporterOf 和一个名为TSupporterOf 的泛型基础实现:

unit GenericSubscriberOfUnit;

interface

uses
  ClassicMessageSubscriberUnit;

type
  ISupporterOf<T: IMessage> = interface(ISubscriber)
    ['{0905B3EB-B17E-4AD2-98E2-16F05D19484C}']
    function Supports(const Message: T): Boolean;
  end;

  ISubscriberOf<T: IMessage> = interface(ISupporterOf<T>)
    ['{6FD82B1D-61C6-4572-BA7D-D70DA9A73285}']
    procedure Consume(const Message: T);
  end;

type
  TSupporterOf<T: IMessage> = class(TInterfacedObject, ISubscriber, ISupporterOf<T>)
    function Supports(const Message: T): Boolean;
  end;

implementation

uses
  SysUtils,
  RttiUnit;

function TSupporterOf<T>.Supports(const Message: T): Boolean;
var
  IID: TGUID;
begin
  if TRtti.GetInterfaceIID<T>(IID) then
    Result := SysUtils.Supports(Message, IID)
  else
    Result := False;
end;

end.

MessageServiceUnit:现在只包含TMessageService、一些类型别名和一些用于管理列表的实际代码,因此我可以实际测试它。

unit MessageServiceUnit;

interface

uses
  Generics.Collections,
  ClassicMessageSubscriberUnit,
  GenericSubscriberOfUnit;

type
  ISubscriberOfIMessage = ISubscriberOf<IMessage>;
  TListISubscriber = TList<ISubscriber>;

  TMessageService = class
  private
    FSubscribers: TListISubscriber;
  strict protected
    procedure Consume(const SubscriberOf: ISubscriberOfIMessage; const Message: IMessage); virtual;
  public
    constructor Create;
    destructor Destroy; override;
    procedure SendMessage(const Message: IMessage);
    procedure Subscribe(const Subscriber: ISubscriber);
    procedure Unsubscribe(const Subscriber: ISubscriber);
  end;

implementation

uses
  SysUtils;

constructor TMessageService.Create;
begin
  inherited Create();
  FSubscribers := TListISubscriber.Create();
end;

destructor TMessageService.Destroy;
begin
  FreeAndNil(FSubscribers);
  inherited Destroy();
end;

procedure TMessageService.SendMessage(const Message: IMessage);
var
  LocalMessage: IMessage;
  lSubscriber: ISubscriber;
  lSubscriberOf: ISubscriberOf<IMessage>;
begin
  for lSubscriber in FSubscribers do
  begin
    LocalMessage := Message; // to prevent premature freeing of Message
    if Supports(lSubscriber, ISubscriberOf<IMessage>, lSubscriberOf) then
      if lSubscriberOf.Supports(LocalMessage) then
        Consume(lSubscriberOf, LocalMessage);
  end;
end;

procedure TMessageService.Subscribe(const Subscriber: ISubscriber);
begin
  FSubscribers.Add(Subscriber);
end;

procedure TMessageService.Unsubscribe(const Subscriber: ISubscriber);
begin
  FSubscribers.Remove(Subscriber);
end;

procedure TMessageService.Consume(const SubscriberOf: ISubscriberOfIMessage; const Message: IMessage);
begin
  SubscriberOf.Consume(Message);
end;

end.

最后是我用来测试所有内容的单元(它使用 http://bo.codeplex.com 的 bo-library):

unit GenericPublishSubscribeMainFormUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, LoggerInterfaceUnit, MessageServiceUnit,
  MessageSubscribersUnit, ClassicMessageSubscriberUnit;

type
  TGenericPublishSubscribeMainForm = class(TForm)
    TestPublisherButton: TButton;
    LogMemo: TMemo;
    procedure TestPublisherButtonClick(Sender: TObject);
  strict private
    FLogger: ILogger;
  strict protected
    function GetLogger: ILogger;
    property Logger: ILogger read GetLogger;
  public
    destructor Destroy; override;
  end;

type
  TLoggingMessageService = class(TMessageService)
  strict private
    FLogger: ILogger;
  strict protected
    procedure Consume(const SubscriberOf: ISubscriberOfIMessage; const Message: IMessage); override;
  public
    constructor Create(const Logger: ILogger);
    property Logger: ILogger read FLogger;
  end;

var
  GenericPublishSubscribeMainForm: TGenericPublishSubscribeMainForm;

implementation

uses
  LoggerUnit,
  OutputDebugViewLoggerUnit,
  LoggersUnit,
  MessagesUnit;

{$R *.dfm}

destructor TGenericPublishSubscribeMainForm.Destroy;
begin
  inherited Destroy;
  FLogger := nil;
end;

function TGenericPublishSubscribeMainForm.GetLogger: ILogger;
begin
  if not Assigned(FLogger) then
    FLogger :=  TTeeLogger.Create([
      TOutputDebugViewLogger.Create(),
      TStringsLogger.Create(LogMemo.Lines)
    ]);
  Result := FLogger;
end;

procedure TGenericPublishSubscribeMainForm.TestPublisherButtonClick(Sender: TObject);
var
  LoggingMessageService: TLoggingMessageService;
begin
  LoggingMessageService := TLoggingMessageService.Create(Logger);
  try
    LoggingMessageService.Subscribe(TMySubscriber.Create() as ISubscriber);
    LoggingMessageService.Subscribe(TMyOtherSubscriber.Create() as ISubscriber);
    LoggingMessageService.SendMessage(TMyMessage.Create());
    LoggingMessageService.SendMessage(TMyOtherMessage.Create());
  finally
    LoggingMessageService.Free;
  end;
end;

constructor TLoggingMessageService.Create(const Logger: ILogger);
begin
  inherited Create();
  FLogger := Logger;
end;

procedure TLoggingMessageService.Consume(const SubscriberOf: ISubscriberOfIMessage; const Message: IMessage);
var
  MessageImplementedWithClass: IImplementedWithClass;
  MessageString: string;
  SubscribeImplementedWithClass: IImplementedWithClass;
  SubscriberOfString: string;
begin
  SubscribeImplementedWithClass := SubscriberOf;
  MessageImplementedWithClass := Message;
  SubscriberOfString := SubscribeImplementedWithClass.ToString;
  MessageString := MessageImplementedWithClass.ToString; // wrong VMT here, Delphi XE SP2
  Logger.Log('Consume(SubscriberOf: %s, Message:%s);',
    [SubscriberOfString, MessageString]);
//    [SubscriberOf.ClassType.ClassName, Message.ClassType.ClassName]);
  inherited Consume(SubscriberOf, Message);
end;

end.

--杰罗恩

旧解决方案:

这可能会做到,但我仍然觉得解决方案有点复杂。

MessageServiceUnitISubscriberOf 现在有一个GUID 和一个Supports 方法来检查IMessage 是否真的被支持。

unit MessageServiceUnit;

interface

uses
  Generics.Collections;

type
  IMessage = interface(IInterface)
    ['{B1794F44-F6EE-4E7B-849A-995F05897E1C}']
  end;

  ISubscriber = interface(IInterface)
    ['{D655967E-90C6-4613-92C5-1E5B53619EE0}']
  end;

  ISubscriberOf<T: IMessage> = interface(ISubscriber)
    ['{6FD82B1D-61C6-4572-BA7D-D70DA9A73285}']
    procedure Consume(const Message: T);
    function Supports(const Message: T): Boolean;
  end;

  TMessageService = class
  private
    FSubscribers: TList<ISubscriber>;
  public
    constructor Create;
    destructor Destroy; override;
    procedure SendMessage(const Message: IMessage);
    procedure Subscribe(const Subscriber: ISubscriber);
    procedure Unsubscribe(const Subscriber: ISubscriber);
  end;

implementation

uses
  SysUtils;

constructor TMessageService.Create;
begin
  inherited Create();
end;

destructor TMessageService.Destroy;
begin
  inherited Destroy();
end;

procedure TMessageService.SendMessage(const Message: IMessage);
var
  lSubscriber: ISubscriber;
  lSubscriberOf: ISubscriberOf<IMessage>;
begin
  for lSubscriber in FSubscribers do
  begin
    if Supports(lSubscriber, ISubscriberOf<IMessage>, lSubscriberOf) then
      if lSubscriberOf.Supports(Message) then
        lSubscriberOf.Consume(Message);
  end;
end;

procedure TMessageService.Subscribe(const Subscriber: ISubscriber);
begin
  FSubscribers.Add(Subscriber);
end;

procedure TMessageService.Unsubscribe(const Subscriber: ISubscriber);
begin
  FSubscribers.Remove(Subscriber);
end;

end.

MessagesUnit:每个消息都有一个interface 和一个GUID,所以Supports 可以检查GUID

unit MessagesUnit;

interface

uses
  MessageServiceUnit;

type
  IMyMessage = interface(IMessage)
    ['{84B42EC8-CAC0-44B4-97A8-05AE5B636236}']
  end;
  TMyMessage = class(TInterfacedObject, IMessage, IMyMessage);

  IMyOtherMessage = interface(IMessage)
    ['{AB323765-FF7B-4852-91AA-B7ECC1845B41}']
  end;
  TMyOtherMessage = class(TInterfacedObject, IMessage, IMyOtherMessage);

implementation

end.

MessageSubscribersUnit:所有订阅者都有一个Supports 方法检查正确的GUID

unit MessageSubscribersUnit;

interface

uses
  MessagesUnit, MessageServiceUnit;

type
  TMySubscriber = class(TInterfacedObject, ISubscriberOf<IMyMessage>)
    procedure Consume(const Message: IMyMessage);
    function Supports(const Message: IMyMessage): Boolean;
  end;

  TMyOtherSubscriber = class(TInterfacedObject, ISubscriberOf<IMyOtherMessage>)
    procedure Consume(const Message: IMyOtherMessage);
    function Supports(const Message: IMyOtherMessage): Boolean;
  end;

implementation

uses
  SysUtils;

procedure TMySubscriber.Consume(const Message: IMyMessage);
begin
  //
end;

function TMySubscriber.Supports(const Message: IMyMessage): Boolean;
begin
  Result := SysUtils.Supports(Message,  IMyMessage);
end;

procedure TMyOtherSubscriber.Consume(const Message: IMyOtherMessage);
begin
  //
end;

function TMyOtherSubscriber.Supports(const Message: IMyOtherMessage): Boolean;
begin
  Result := SysUtils.Supports(Message,  IMyOtherMessage);
end;

end.

MessagesUnit:包含特定的消息(接口类型和类类型),其中包含 IID GUID,以便与 Supports 区分开来。

unit MessagesUnit;

interface

uses
  MessageServiceUnit,
  ClassicMessageSubscriberUnit;

type
  IMyMessage = interface(IMessage)
    ['{84B42EC8-CAC0-44B4-97A8-05AE5B636236}']
  end;
  TMyMessage = class(TInterfacedObject, IMessage, IMyMessage);

  IMyOtherMessage = interface(IMessage)
    ['{AB323765-FF7B-4852-91AA-B7ECC1845B41}']
  end;
  TMyOtherMessage = class(TInterfacedObject, IMessage, IMyOtherMessage);

implementation

end.

MessageSubscribersUnit:包含特定的订阅者(接口类型和类类型),现在不再需要Supports 方法:它们只包含Consume 方法。

unit MessageSubscribersUnit;

interface

uses
  MessagesUnit,
  MessageServiceUnit,
  GenericSubscriberOfUnit,
  ClassicMessageSubscriberUnit;

type
  TMySubscriber = class(TSupporterOf<IMyMessage>, ISubscriber, ISubscriberOf<IMyMessage>)
    procedure Consume(const Message: IMyMessage);
  end;

  TMyOtherSubscriber = class(TSupporterOf<IMyOtherMessage>, ISubscriber, ISubscriberOf<IMyOtherMessage>)
    procedure Consume(const Message: IMyOtherMessage);
  end;

implementation

uses
  SysUtils;

procedure TMySubscriber.Consume(const Message: IMyMessage);
begin
  //
end;

procedure TMyOtherSubscriber.Consume(const Message: IMyOtherMessage);
begin
  //
end;

end.

--杰罗恩

【讨论】:

  • 成功了,谢谢!我希望有一种方法可以避免每条消息使用两种方法和一个接口,但我认为这是不可能的。
猜你喜欢
  • 2019-07-21
  • 1970-01-01
  • 2012-06-09
  • 2013-10-15
  • 2020-03-22
  • 2018-07-23
  • 2020-11-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多