Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructor injection versus setter injection for Parent property

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.

like image 913
Rick Wheeler Avatar asked Aug 11 '15 05:08

Rick Wheeler


1 Answers

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).

like image 66
Stefan Glienke Avatar answered Oct 13 '22 21:10

Stefan Glienke