Search code examples
delphicontainersspring4d

Spring4d - Constructor with interface and object as parameters


For example I have 3 frames like this:

TBaseFrame = class(TFrame)
end;

TfraDataAwareEntity = class(TBaseFrame )
  private
    FEntity: TObject;
    procedure SetEntity(const Value: TObject);
    { Private declarations }
  public
    constructor Create(AOwner: TComponent;
                       ADataset: IDataset;
                       AEntity: TObject); 
    property Entity: TObject read FEntity write SetEntity;
  end;

TfraDataAwareEntityWithGrid = class(TBaseFrame )
  private
    FEntity: TObject;
    procedure SetEntity(const Value: TObject);
    { Private declarations }
  public
    constructor Create(AOwner: TComponent;
                       ADataset: IDataset;
                       AEntity: TObject); 
    property Entity: TObject read FEntity write SetEntity;
  end;

Interface:

IDataset = interface
  ['{065771AA-A114-4D01-B180-427A39DD51D7}']
     procedure Save;
     procedure Load;
end;

Implementation:

TCustomDataset = class(TInterfacedObject, IDataset)
    procedure Save;
    procedure Load;
end;

Registration:

 GlobalContainer.RegisterType<TBaseFrame , TfraDataAwareEntity >
    ('TfraDataAwareEntity ');
 GlobalContainer.RegisterType<TBaseFrame , TfraDataAwareEntityWithGrid >
    ('TfraDataAwareEntityWithGrid ');
 GlobalContainer.RegisterType<TCustomDataset >.Implements<IDataset>.AsDefault;

Now I want to create a frame:

 GlobalContainer.Resolve<TBaseFrame>('TfraDataAwareEntity ',
      [ Self,
        GlobalContainer.Resolve<IDataset>,
        SomeObj ]);

The problem with the code above, it doesn't accept an Interface as parameter (error: There is no overloaded version of 'TContainer.Resolve<FrameUnit.TfraDataAwareEntity >' that can be called with these arguments), I need to pass TCustomDataset but if I have a factory that returns an IDataSet I cant use it, and I don't want the unit to be coupled with TCustomDataset, just IDataSet. So How I go about it?

Another option I tried would be inject TCustomDataset on TfraDataAwareEntity constructor (like this example: "Unsatisfied constructor" on constructor injection with Spring4D )but then I get an error that cant resolve TComponent, is there a way to inject TCustomDataset in IDataset in the constructor and also pass the rest of parameters?


Solution

  • You need to wrap the values into TValue.From(...) (you can omit the specific type in <..> because type inference will take care of it) because

    • There is no implicit overload in TValue that takes an interface, hence the admittedly misleading compiler error
    • The implicit overloads for Self and SomeObj might cause non exact types which will cause the constructor lookup to fail.

    I also suggest you register this as factory function in your container to not have the code you posted in your consumer code - that would not be dependency injection but rather the anti pattern of service locator because you gained nothing but traded constructor calls with resolve calls.

    The missing code to the one you posted:

    type
      TFrameFactory = reference to function(const AFrameName: string; AOwner: TComponent; AEntity: TObject): TBaseFrame;
    
    
      GlobalContainer.RegisterInstance<TFrameFactory>(
        function(const AFrameName: string; AOwner: TComponent; AEntity: TObject): TBaseFrame
        begin
          Result := GlobalContainer.Resolve<TBaseFrame>(AFrameName,
            [TValue.From(AOwner), TValue.From(GlobalContainer.Resolve<IDataset>), TValue.From(AEntity)]);
        end);
    

    You can then get TFrameFactory injected into the component where you want to be able to construct your frames.

    Note about future plans for Spring:

    • supporting partial injection - that means when not explicitly passing the value for a parameter the container knows how to resolve itself (such as the IDataset) it will be automatically resolved by the container which avoids explicit resolve calls to provide all parameters.
    • support for metainformation - currently you can only identify registered services by their name which can be a bit cumbersome. This feature will allow identifying them by things like an enum.