[英]How to test the type of a generic interface?
I'm not sure if the title makes sense, but I hope you can understand my question with some code. 我不确定标题是否有意义,但我希望你能用一些代码理解我的问题。
Given the following code for a publish/subscribe framework. 给出了发布/订阅框架的以下代码。
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;
That would be used like this: 那会像这样使用:
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;
How can I loop the subscribers list and send the message to the proper subscribers? 如何循环订阅者列表并将消息发送给适当的订阅者?
The subscribers list will have all subscribers for all types of messages. 订阅者列表将包含所有类型消息的所有订阅者。 The SendMessage have to find the subscribers for the type of message provided as param and send it to whom implements the proper interface to consume that type of message.
SendMessage必须找到作为param提供的消息类型的订阅者,并将其发送给实现适当接口的消费者,以使用该类型的消息。
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;
Thanks! 谢谢!
BTW, this code is based on this blog post . 顺便说一句,此代码基于此博客文章 。
Edit : found a way to make this less convoluted (please vote on this answer as you like this; it took quite a while to get it right). 编辑 :找到一种方法可以减少这种错误(请按照您的喜好对此答案进行投票;它需要很长时间才能正确完成)。
Note it uses the new Rtti
unit, so it works only for Delphi 2010 and up (I used Delphi XE for developing this, I did not yet verify this in Delphi 2010). 请注意它使用新的
Rtti
单元,因此它仅适用于Delphi 2010及更高版本(我使用Delphi XE进行开发,我还没有在Delphi 2010中验证这一点)。
For the Supports
, you need to store some IID GUIDs with your interfaces and a means to query them . 对于
Supports
,您需要使用您的接口存储一些IID GUID 以及查询它们的方法 。
Since you want to use this with generics, you want to able to query the IID GUID from an interface type, not from an interface reference (as Hallvard Vassbotn showed with a hack in 2006 ). 由于您希望将此与泛型一起使用,因此您希望能够从接口类型查询IID GUID,而不是从接口引用中查询(正如Hallvard Vassbotn在2006年的hack中所示 )。
The new RTTI introduced in Delphi 2010 allows you to do just that: 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.
So now the changed code, which I rearranged a bit, and spread over more units to keep the overview. 所以现在改变了代码,我重新安排了一些,并扩展到更多单元以保持概述。
ClassicMessageSubscriberUnit : has the non generic interfaces IMessage
and ISubscriber
(they descend from IImplementedWithClass
which makes it easier to log things. ClassicMessageSubscriberUnit :具有非通用接口
IMessage
和ISubscriber
(它们来自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 : contains the generic ISubscriberOf
interface which descends from the generic ISupporterOf
and a generic base implementation called TSupporterOf
: 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 : now only contains TMessageService
, some type aliases and some actual code for managing the list so I could actually test it. 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.
Finally a unit that I used to test everything (it uses the bo-library at http://bo.codeplex.com ): 最后我用来测试一切的单元(它使用http://bo.codeplex.com上的bo库):
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.
--jeroen --jeroen
Old solution: 旧解决方案:
This might do it, but I still find the solution a bit convoluted.
这可能会这样做,但我仍然觉得解决方案有点复杂。
MessageServiceUnit :
ISubscriberOf
now has a
GUID
and a
Supports
method to check if the
IMessage
is in fact supported.
MessageServiceUnit :
ISubscriberOf
现在有一个
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 : Messages each have an
interface
with a
GUID
so
Supports
can check for the
GUID
.
MessagesUnit :每个消息都有一个带
GUID
的
interface
,因此
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 : all subscribers have a
Supports
method checking the right
GUID
.
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 : contains the specific messages (both the interface and class types), which contain the IID GUIDs to distinguish them with
Supports
.
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 : contains the specific subscribers (both the interface and class types), which now do not need the
Supports
method any more: they only contain the
Consume
method.
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.
--jeroen
--jeroen
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.