I'm trying to figure out the best way to use dependency injection for some legacy code which will take a long to refactor and has to be done gradually. Most of the old classes use a "Parent" property for determining various things and the parent property was often pass in via a constructor argument as follows:
constructor TParentObject.Create;
begin
FChildObject := TChildObject.Create(Self);
end;
constructor TChildObject.Create(AParent: TParentObject)
begin
FParent := AParent;
end;
This is fairly typical of our legacy code base. However when moving to interfaces and constructor injection, the Parent is not known by the Spring4D framework when creating the Child object. So it will just get a new parent but not the existing one. Of course I can create a property getter/setter but this would indicate an "optional" property for the class which is really a mandatory property. See the code below for more explanation:
unit uInterfaces;
interface
uses
Spring.Collections;
type
IChildObject = interface;
IParentObject = interface
['{8EA8F9A2-E627-4546-8008-0A77DA2B16F1}']
function GetSomethingRequiredByChild: string;
procedure SetSomethingRequiredByChild(const Value: string);
property SomethingRequiredByChild: string read GetSomethingRequiredByChild write SetSomethingRequiredByChild;
function GetChild: IChildObject;
property Child: IChildObject read GetChild;
end;
// This introduces a property getter/setter
// However it also implies that Parent can be NIL which it cannot
IChildObject = interface
['{ECCA09A6-4A52-4BE4-A72E-2801160A9086}']
function GetParent: IParentObject;
procedure SetParent(const Value: IParentObject);
property Parent: IParentObject read GetParent write SetParent;
end;
TParentObject = class(TInterfacedObject, IParentObject)
private
FChild: IChildObject;
FSomethingRequiredByChild: string;
function GetChild: IChildObject;
function GetSomethingRequiredByChild: string;
procedure SetSomethingRequiredByChild(const Value: string);
public
constructor Create;
end;
TChildObject = class(TInterfacedObject, IChildObject)
private
FParent: IParentObject;
function GetParent: IParentObject;
procedure SetParent(const Value: IParentObject);
public
// This requries a Parent object, but how does the Spring4D resolve the correct parent?
constructor Create(const AParent: IParentObject);
end;
implementation
uses
Spring.Services;
{ TParentObject }
constructor TParentObject.Create;
begin
// Here is the old way...
FChild := TChildObject.Create(Self); // Old way of doing it
// This is the Service Locator way...
FChild := ServiceLocator.GetService<IChildObject>;
// I would prefer that the Parent is assigned somehow by the Service Locator
// IS THIS POSSIBLE - or am I dreaming?
FChild.Parent := Self;
end;
function TParentObject.GetChild: IChildObject;
begin
Result := FChild;
end;
function TParentObject.GetSomethingRequiredByChild: string;
begin
Result := FSomethingRequiredByChild;
end;
procedure TParentObject.SetSomethingRequiredByChild(const Value: string);
begin
FSomethingRequiredByChild := Value;
end;
{ TChildObject }
constructor TChildObject.Create(const AParent: IParentObject);
begin
FParent := AParent;
end;
function TChildObject.GetParent: IParentObject;
begin
Result := FParent;
end;
procedure TChildObject.SetParent(const Value: IParentObject);
begin
FParent := Value;
end;
end.
Maybe there is some methodology which can be used that I'm not aware of to set the parent object using the DI framework?
I hope this question is clear what I'm trying to achieve. I'm happy to provide more description/code example where necessary.
First of all you should not use the service locator to replace ctor calls. This is just making things worse. I know people think they are smart by doing that but really you are replacing one simple dependency on another class with a dependency on some global state plus the requirement that some other code out of (the consuming classes) control puts the dependency into the container. That does not result in easier but harder to maintain code.
Plus all the other reasons why you should stay away from it. The service locator might have a limited use in legacy application to introduce a composition root in the middle of an application to start DI from that point on but not in a way you show.
If parent needs the child then just inject it. Now the problem is if you want to create a parent, you first need the child but the child needs the parent. How to achieve that? There are two solutions. However one of them is not pure DI compatible.
I first show the way using a factory provided by the container (needs latest develop branch version as of the time of posting):
unit ParentChildRelationShip.Types;
interface
uses
SysUtils,
Spring,
Spring.Container.Common;
type
IChildObject = interface;
IParentObject = interface
['{8EA8F9A2-E627-4546-8008-0A77DA2B16F1}']
function GetChild: IChildObject;
property Child: IChildObject read GetChild;
end;
IChildObject = interface
['{ECCA09A6-4A52-4BE4-A72E-2801160A9086}']
function GetParent: IParentObject;
property Parent: IParentObject read GetParent;
end;
TParentObject = class(TInterfacedObject, IParentObject)
private
FChild: IChildObject;
function GetChild: IChildObject;
public
constructor Create(const childFactory: IFactory<IParentObject, IChildObject>);
end;
TChildObject = class(TInterfacedObject, IChildObject)
private
FParent: WeakReference<IParentObject>;
function GetParent: IParentObject;
public
constructor Create(const AParent: IParentObject);
end;
implementation
{ TParentObject }
constructor TParentObject.Create;
begin
FChild := childFactory(Self);
end;
function TParentObject.GetChild: IChildObject;
begin
Result := FChild;
end;
{ TChildObject }
constructor TChildObject.Create(const AParent: IParentObject);
begin
FParent := AParent;
end;
function TChildObject.GetParent: IParentObject;
begin
Result := FParent;
end;
end.
program ParentChildRelation;
{$APPTYPE CONSOLE}
uses
SysUtils,
Spring.Container,
Spring.Container.Common,
ParentChildRelationShip.Types in 'ParentChildRelationShip.Types.pas';
procedure Main;
var
parent: IParentObject;
child: IChildObject;
begin
GlobalContainer.RegisterType<IParentObject,TParentObject>;
GlobalContainer.RegisterType<IChildObject,TChildObject>;
GlobalContainer.RegisterFactory<IFactory<IParentObject,IChildObject>>(TParamResolution.ByValue);
GlobalContainer.Build;
parent := GlobalContainer.Resolve<IParentObject>;
child := parent.Child;
Assert(parent = child.Parent);
end;
begin
try
Main;
except
on E: Exception do
Writeln(E.Message);
end;
ReportMemoryLeaksOnShutdown := True;
end.
If you don't want to use a container provided factory you explicitly register it yourself. Then the RegisterFactory call is replaced with this one:
GlobalContainer.RegisterInstance<TFunc<IParentObject,IChildObject>>(
function(parent: IParentObject): IChildObject
begin
Result := GlobalContainer.Resolve<IChildObject>([TValue.From(parent)]);
end);
And the constructor parameter can be changed to TFunc<...>
as it does not need RTTI for this method (that is why you needed IFactory<...>
in the other case).
The second version uses field injection and thus is pure DI incompatible - be careful writing code like that as it does not work without using the container or RTTI - like if you want to test these classes it might become hard to compose them without the container. The important part here is the PerResolve which tells the container to reuse the once resolved instance whenever another dependency is needed that it can satisfy.
unit ParentChildRelationShip.Types;
interface
uses
SysUtils,
Spring;
type
IChildObject = interface;
IParentObject = interface
['{8EA8F9A2-E627-4546-8008-0A77DA2B16F1}']
function GetChild: IChildObject;
property Child: IChildObject read GetChild;
end;
IChildObject = interface
['{ECCA09A6-4A52-4BE4-A72E-2801160A9086}']
function GetParent: IParentObject;
property Parent: IParentObject read GetParent;
end;
TParentObject = class(TInterfacedObject, IParentObject)
private
[Inject]
FChild: IChildObject;
function GetChild: IChildObject;
end;
TChildObject = class(TInterfacedObject, IChildObject)
private
FParent: WeakReference<IParentObject>;
function GetParent: IParentObject;
public
constructor Create(const AParent: IParentObject);
end;
implementation
function TParentObject.GetChild: IChildObject;
begin
Result := FChild;
end;
{ TChildObject }
constructor TChildObject.Create(const AParent: IParentObject);
begin
FParent := AParent;
end;
function TChildObject.GetParent: IParentObject;
begin
Result := FParent;
end;
end.
program ParentChildRelation;
{$APPTYPE CONSOLE}
uses
SysUtils,
Spring.Container,
Spring.Container.Common,
ParentChildRelationShip.Types in 'ParentChildRelationShip.Types.pas';
procedure Main;
var
parent: IParentObject;
child: IChildObject;
begin
GlobalContainer.RegisterType<IParentObject,TParentObject>.PerResolve;
GlobalContainer.RegisterType<IChildObject,TChildObject>;
GlobalContainer.Build;
parent := GlobalContainer.Resolve<IParentObject>;
child := parent.Child;
Assert(parent = child.Parent);
end;
begin
try
Main;
except
on E: Exception do
Writeln(E.Message);
end;
ReportMemoryLeaksOnShutdown := True;
end.
By the way. Watch your references between parent and child when using interfaces. If they reference each other you will get memory leaks. You can solve that by using a weak reference on one side (usually the parent reference in the child).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With