编辑:找到了一种减少复杂性的方法(请按您喜欢的方式对这个答案投票;花了很长时间才弄对)。
请注意,它使用新的 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:具有非通用接口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:包含泛型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.
--杰罗恩
旧解决方案:
这可能会做到,但我仍然觉得解决方案有点复杂。
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:每个消息都有一个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.
--杰罗恩