首页 > 解决方案 > 是否可以为 Delphi 创建一个通用调解器来处理通用命令

问题描述

我必须首先承认我来自 .Net 世界,目前正在重新学习 Delphi (XE 10.x)(从高中开始 - 许多年前)。在 .Net 中,MediatRMassTransit等库可以很好地处理中介模式。然而,我发现很少有库支持 Delphi 中中介者模式的动态(或半动态)实现。在不去扫描正在执行的 Rtti 信息的花哨级别的情况下,我想创建一个简单的中介,我可以在其中通过 Request 注册一个 CommandHandler,然后得到响应。这可能吗?

这是迄今为止我制作的一些示例代码 - 但我只是陷入了如何动态创建对象以及我的方法是否合理的问题上。

在检查代码之前,我并没有坚持使用 TDictionary<string, string> 来注册类型,但是,我对 Rtti 的了解有限,很难确定它应该使用 TClass 还是 TRttiTypes。如果其中任何一个有帮助,我将不胜感激。

// interface

uses
  System.Generics.Collections;

type
  TUnit = record
  end;

  IRequest<TResponse> = interface
  end;

  IRequest = interface(IRequest<TUnit>)
  end;

  IRequestHandler<TResponse; TRequest: IRequest<IResponse>> = interface(IInvokable)
    function Handle(ARequest: TRequest): TResponse;
  end;

  IRequestHandler<TRequest: IRequest<TUnit>> = interface(IRequestHandler<TUnit, TRequest>)
  end;

  TMediator = class
  private
    FRequestHandlers: TDictionary<string, string>;
  public
    constructor Create;
    destructor Destroy; override;
    procedure RegisterHandler(AHandlerClass, ARequestClass: TClass);
    function Send<TResponse, TRequest>(ARequest: TRequest): TResponse;
  end;

// implementation

constructor TMediator.Create;
begin
  Self.FRequestHandlers := TDictionary<string, string>.Create;
end;

destructor TMediator.Destroy;
begin
  Self.FRequestHandlers.Free;
  inherited;
end;

procedure TMediator.RegisterHandler(AHandlerClass, ARequestClass: TClass);
var
  LTempRequestClass : string;
  rContext          : TRttiContext;
  rType             : TRttiType;
begin
  if Self.FRequestHandlers.TryGetValue(ARequestClass.QualifiedClassName, LTempRequestClass) then
    exit;

  { I would like to add some error checking functionality to prevent classes 
    that do not implement IRequest or IRequest<> from being added here. }

  Self.FRequestHandlers.Add(ARequestClass.QualifiedClassName, AHandlerClass.QualifiedClassName);
end;

function TMediator.Send<TResponse, TRequest>(ARequest: TRequest): TResponse;
var
  LRequestHandlerClassName: string;
  LRequestHandler   : IRequestHandler<TResponse, TRequest>;
begin
  if not Self.FRequestHandlers.TryGetValue(ARequest.QualifiedClassName, LRequestHandlerClassName) then
    raise Exception.Create('Handler class not registered with this mediator.');

  { Not sure what to do here to get the LRequestHandler - I'm also using Spring4d, 
    so I considered using the QualifiedClassName as a way to resolve classes 
    registered in the TContainer }

  Result := LRequestHandler.Handle(ARequest);

end;

我对此的预期用法是:

注意:下面的编辑 - 我希望能够从单个主持人注册和调用任何实现 IRequest 或 IRequest<> 的命令。

// interface

type
  TMyResponse = class
  private
    FFoo: string;
  public
   property Foo: string read FFoo write FFoo;
  end;

  TMyResponse2 = class
  private
    FFoo2: string;
  public
   property Foo2: string read FFoo2 write FFoo2;
  end;
  
  TMyRequest = class(TInterfacedObject, IRequest<TMyResponse>)
  private
    FBar: string;
  public
    property Bar: string read FBar write FBar;
  end;

  TMyRequest2 = class(TInterfacedObject, IRequest<TMyResponse2>)
  private
    FBar2: string;
  public
    property Bar2: string read FBar2 write FBar2;
  end;

  TMyRequestHandler = class(TInterfacedObject, IRequestHandler<TMyResponse, TMyRequest>)
  public
    function Handle(ARequest: TMyRequest): TMyResponse;
  end;

  TMyRequestHandler2 = class(TInterfacedObject, IRequestHandler<TMyResponse2, TMyRequest2>)
  public
    function Handle(ARequest: TMyRequest2): TMyResponse2;
  end;


// implementation

var 
  AMediator: TMediator;
  ARequest: TMyRequest;
  ARequest2: TMyRequest2;
  AResponse: TMyResponse;
  AResponse2: TMyResponse2;
begin
  AMediator := TMediator.Create;
  ARequest := TMyRequest.Create;
  ARequest2 := TMyRequest2.Create;
  try
    ARequest.Bar := 'something';
    ARequest2.Bar2 := 'else';

    // Not sure how I would get these either - seems best to use the qualified class name
    AMediator.Register(TMyRequestHandler.QualifiedClassName, TMyRequest.QualifiedClassName);
    AMediator.Register(TMyRequestHandler2.QualifiedClassName, TMyRequest2.QualifiedClassName);

    AResponse := AMediator.Send(ARequest);
    AResponse2 := AMediator.Send(ARequest2);

    // Do something with this value
  finally
    AResponse2.Free;
    AResponse.Free;
    ARequest2.Free;
    ARequest.Free;
    AMediator.Free;
  end;
end.

标签: delphidesign-patternsdelphi-10.3-riospring4d

解决方案


所以,我似乎走错了路,多亏了J……他让我重新思考我在做什么。总之,我试图让一些东西充当依赖注入层,以便能够根据给定的“请求”动态运行“处理程序”。最后,似乎简单的解决方案是调用我已经用来执行该功能的 Spring4d DI 层。我仍然觉得有一些相当紧密的耦合,但我目前对结果感到满意。这是代码:

CQRSU.pas

unit CQRSU;

interface

uses
  System.Generics.Collections,
  Spring.Container;

type
  TUnit = record
  end;

  IBaseRequest = interface(IInvokable)
    ['GUID']
  end;

  IRequest<TResponse> = interface(IBaseRequest)
    ['GUID']
  end;

  IRequest = interface(IRequest<TUnit>)
    ['GUID']
  end;

  IRequestHandler<TResponse; TRequest: IRequest<TResponse>> = interface(IInvokable)
    ['GUID']
    function Handle(ARequest: TRequest): TResponse;
  end;

  IRequestHandler<TRequest: IRequest<TUnit>> = interface(IRequestHandler<TUnit, TRequest>)
    ['GUID']
  end;

implementation

end.

服务U.pas

unit ServicesU;

interface

  uses
    CQRSU;

  type
    TMyResponse = class
    private
      FMyResult: string;
    public
      property MyResult: string read FMyResult write FMyResult;
    end;

    TMyRequest = class(TInterfacedObject, IRequest<TMyResponse>)
    private
      FMyParameter: string;
    public
      property MyParameter: string read FMyParameter write FMyParameter;
    end;

    TMyRequestHandler = class(TInterfacedObject, IRequestHandler<TMyResponse, TMyRequest>)
    public
      function Handle(ARequest: TMyRequest): TMyResponse;
    end;

implementation

  { TMyRequestHandler }

  function TMyRequestHandler.Handle(ARequest: TMyRequest): TMyResponse;
  begin
    Result := TMyResponse.Create;
    Result.MyResult := ARequest.MyParameter + ' Processed';
  end;

end.

测试CQRS.dpr

program TestCQRS;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  Spring.Container,
  System.SysUtils,
  CQRSU in 'CQRSU.pas',
  ServicesU in 'ServicesU.pas';

var
  LContainer:        TContainer;
  LMyRequestHandler: IRequestHandler<TMyResponse, TMyRequest>;
  LRequest:          TMyRequest;
  LResponse:         TMyResponse;

begin
  LContainer := TContainer.Create;
  try
    LRequest             := TMyRequest.Create;
    LRequest.MyParameter := 'Hello there!';
    try
      LContainer.RegisterType<TMyRequestHandler>.Implements<IRequestHandler<TMyResponse, TMyRequest>>;
      LContainer.Build;
      LMyRequestHandler := LContainer.Resolve<IRequestHandler<TMyResponse, TMyRequest>>;
      LResponse         := LMyRequestHandler.Handle(LRequest);
      writeln(LResponse.MyResult);
      readln;
    except
      on E: Exception do
        writeln(E.ClassName, ': ', E.Message);
    end;
  finally
    if Assigned(LResponse) then
      LResponse.Free;
    if Assigned(LRequest) then
      LRequest.Free;
    LContainer.Free;
  end;

end.

推荐阅读