3

使用 Spring4D 时,如何在调用 GlobalContainer 时将字符串值作为参数传递。解析以便在已解析的类构造函数上使用此字符串值?

我想解析一个映射到 TWorker 的类 IWorker。TWorker 类在它的构造函数中依赖于 ITool 加上一个用于工作人员名称的字符串。

我猜答案在于可以作为参数提供给 GlobalContainer.Resolve 的 TValue 数组,但我不明白如何使用它。

我发现这篇关于在调用 GlobalContainer.Resolve 时使用TParameterOverride作为参数的帖子可能有效,但这个功能似乎在 Spring4D 的 1.1 版本中消失了。

我想在注册我的类型时避免调用 InjectConstructor。

我需要帮助的部分是

GlobalContainer.Resolve<IWorker>([{what do I put here?}]).Work;

这是我的一个小项目

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  Spring.Container;

type
   IWorker = interface
   ['{2BBD7E9C-4806-4F01-9B05-9E9DD928D21D}']
      procedure Work;
   end;

   ITool = interface
   ['{F962209D-4BC3-41C4-9089-0A874632ED1A}']
      procedure Use;
   end;

   TWorker = class(TInterfacedObject, IWorker)
   private
      FTool: ITool;
      FName: string;
      procedure Work;
   public
      constructor Create(tool: ITool; name: string);
   end;

   THammer = class(TInterfacedObject, ITool)
   private
      procedure Use;
   end;

{ TWorker }
constructor TWorker.Create(tool: ITool; name: string);
begin
   FTool := tool;
   FName := name;
end;

procedure TWorker.Work;
begin
   Writeln(FName + ' is working');
   FTool.Use;
end;


{ THammer }
procedure THammer.Use;
begin
   Writeln('Using a hammer');
end;


begin
   try
      GlobalContainer.RegisterType<ITool, THammer>;
      GlobalContainer.RegisterType<IWorker, TWorker>; // TWorker constructor = Create(tool: ITool; name: string);
      GlobalContainer.Build;

      GlobalContainer.Resolve<IWorker>([{what do I put here?}]).Work;
      GlobalContainer.Resolve<IWorker>(['THammer.Create', 'Bob']).Work; //--> 'Unsatisfied constructor on type: TWorker'
      GlobalContainer.Resolve<IWorker>([THammer.Create, 'Bob']).Work; //--> Access violation
      GlobalContainer.Resolve<IWorker>([nil, 'Bob']).Work; //--> 'Unsatisfied constructor on type: TWorker'
      Readln;
   except
      on E: Exception do
      begin
         Writeln(E.ClassName, ': ', E.Message);
         Readln;
      end;
   end;
end.

帮助将不胜感激。谢谢!

4

3 回答 3

7

正如 Sam 所说,您应该避免在整个代码中使用容器作为服务定位器,因为这只是将构造函数调用替换为对容器的调用,这会导致比所有硬连线的代码更糟糕的代码。

虽然可以将参数传递给 Resolve 调用,但它确实应该通过使用工厂来解决。

这将是如何为 name 参数传递值(该工具由容器注入,因为它知道这一点(TNamedValue在 中声明Spring.pas)。

GlobalContainer.Resolve<IWorker>([TNamedValue.Create('name', 'Bob')]).Work;

但是我们可以将该代码与注册工厂结合起来(不幸的是,因为 RTTI 缺少关于类型是我们必须使用的匿名方法类型的信息TFunc<...>

type
  TWorkerFactory = TFunc<string, IWorker>;

...

GlobalContainer.RegisterType<ITool, THammer>;
GlobalContainer.RegisterType<IWorker, TWorker>;
GlobalContainer.RegisterInstance<TWorkerFactory>(
  function (name: string): IWorker
  begin
    Result := GlobalContainer.Resolve<IWorker>([TNamedValue.Create('name', name)]);
  end);
GlobalContainer.Build;

GlobalContainer.Resolve<TWorkerFactory>.Invoke('Bob').Work;

因此,这使您可以TWorkerFactory在代码中的某个位置放置一个参数,然后容器可以将其注入。这样,您就可以使用依赖注入解耦代码,但对容器没有任何直接依赖(实际上,您仍然可以手动连接所有内容,这是我之前所说的规则)

在 1.2 版本中,容器将支持自动创建工厂,因此您可以编写如下代码:

type
  {$M+}
  TWorkerFactory = reference to function(const name: string): IWorker;

...

GlobalContainer.RegisterFactory<TWorkerFactory>;

这会自动创建一个代理,将工厂方法的参数进一步传递到容器中。

于 2015-04-10T07:18:04.820 回答
1

通常的解决方案是在容器中为工人注册工厂,然后要求工厂使用特定工具和字符串(名称?)返回工人。

您当前的代码看起来可能希望在应用程序中使用容器,这是一种味道,因为容器应该只在组合根目录中使用

于 2015-04-07T14:53:21.043 回答
1

我知道应该避免这种情况,但我找到了一种使用我的字符串参数调用 resolve 的方法。

以下代码有效,但不是一个好主意:

  GlobalContainer.RegisterType<ITool, THammer>;
  GlobalContainer.RegisterInstance<TFunc<string, IWorker>>(
     function(workerName: string): IWorker
     begin
           Result := TWorker.Create(GlobalContainer.Resolve<ITool>, workerName);
     end);
  GlobalContainer.Build;

  GlobalContainer.Resolve<TFunc<string, IWorker>>.Invoke('Bob').Work;
于 2015-04-07T22:24:46.587 回答