如何在调用Spring4D的GlobalContainer.Resolve时将字符串值作为参数传递?

3
使用Spring4D时,我如何在调用GlobalContainer.Resolve时将字符串值作为参数传递,以便该字符串值在已解析的类构造函数中使用?
我想要解析映射到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.

需要帮助,谢谢!

3个回答

7
如同Sam所说的,您应该避免在整个代码中使用容器作为服务定位器,因为这只是将构造函数调用替换为对容器的调用,从而导致更糟糕的代码。虽然可以通过Resolve调用传递参数,但真正的解决方法应该是使用工厂模式。
以下是如何为名称参数传递值的示例(由于容器知道该工具(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>;

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


Stefan,你能给我们分享一个例子吗? TWorkerFactory如何被连接? - Turrican

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;

1
通常的解决方案是在容器中注册一个工厂来为工人提供服务,然后请求工厂使用特定的工具和字符串(名称?)返回一个工人。
你当前的代码似乎期望在应用程序内部使用容器,这是一种不好的做法,因为容器应该只在组合根中使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接