When using Spring4D, how can I pass a string value as a parameter when calling GlobalContainer. Resolve so that this string value is used on the resolved class constructor?
I want to resolve a class IWorker that is mapped to a TWorker. The TWorker class has a dependency in it's constructor to an ITool plus a string for the worker's name.
I would guess the answer lies in the array of TValue that can be given as a parameter to GlobalContainer.Resolve but I do not understand how to use it.
I found this post about using a TParameterOverride as a parameter when calling GlobalContainer.Resolve that might have worked but this functionality seems to have disappeared in the 1.1 version of Spring4D.
I want to avoid a call to InjectConstructor when registering my type.
The part where I need help is
GlobalContainer.Resolve<IWorker>([{what do I put here?}]).Work;
Here is my a small project
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.
Help would be appreciated. Thanks!
As Sam said you should avoid using the container as service locator throughout your code as that is just a replacement of a constructor call to a call to the container which leads to even worse code than everything hardwired.
While it is possible to pass arguments to the Resolve call it really should be solved by using factories.
This would be how to pass a value for the name argument (the tool is injected by the container since it knows about that (TNamedValue
is declared in Spring.pas
).
GlobalContainer.Resolve<IWorker>([TNamedValue.Create('name', 'Bob')]).Work;
But we can combine that code with registering a factory (unfortunately because the RTTI is lacking information about a type being an anonymous method type we have to use 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;
So this enables you putting a TWorkerFactory
argument somewhere in your code where the container then can inject it. That way you have decoupled code using dependency injection but without any direct dependency on the container (in fact you could still wire up everything manually which is the rule as I stated previously)
With the 1.2 release the container will support automatic factory creation so you can write code like this:
type
{$M+}
TWorkerFactory = reference to function(const name: string): IWorker;
...
GlobalContainer.RegisterFactory<TWorkerFactory>;
This automatically creates a proxy that passes the arguments of the factory method further into the container.