Search code examples
delphiinterfacespring4d

Delphi: Invoke parent constructor using interface (Spring4D framework)


I am faced with the problem of properly object instantiating from the type resolved by a Spring4D framework container.

I have a class:

type
  TSurvey = class ( TInterfacedObject, ISurvey )

  private
        _id : Integer;
        _organization : IOrganization;

        function GetId () : Integer;
        procedure SetId ( const value : Integer );

        function GetOrganization () : IOrganization;
        procedure SetOrganization ( const value : IOrganization);

  public
        property Id : Integer read GetId write SetId;
        property Organization: IOrganization read GetOrganization write SetOrganization;
end;

...

initialization

  GlobalContainer.RegisterType<TSurvey>.Implements<ISurvey>.InjectField ( '_organization' );

...

I use the GlobalContainer to instantiate an object:

survey := GlobalContainer.Resolve<ISurvey>;
survey.Organization.Id := 5;

and everything is alright and works perfectly.

Now I want to create a descendant class for TSurvey:

type
  TFieldSurvey = class ( TSurvey )
  ...
end;

And the question is how to correct instantiate an object for TFieldSurvey class?

If I use Create (), then I get an exception:

 fieldSurvey := TFieldSurvey.Create ();
 fieldSurvey.Organization.Id := 5    <- exception is here

Do I have to explicitly call the constructor for Organization field in TFieldSurvey constructor, or there is another way? For example, using GlobalContainer?

Thanks in advance.


Solution

  • The Injection will only work when you create your object through the Container, not by directly calling the constructor on your object. So you would need to register TFieldSurvey with GlobalContainer and then call Resolve to get your object.

    Register:

    GlobalContainer.RegisterType<TSurvey>.Implements<ISurvey>('SPRING_SURVEY').InjectField ( '_organization' );
    GlobalContainer.RegisterType<TFieldSurvey>.Implements<ISurvey>('SPRING_FIELD_SURVEY').InjectField ( '_organization' );
    

    Then to obtain an instance:

    GlobalContainer.Resolve<ISurvey>('SPRING_FIELD_SURVEY')
    

    I added the names of 'SPRING_SURVEY' and 'SPRING_FIELD_SURVEY' since they both implement ISurvey and this lets you choose which class instance you want otherwise you end up with the last implementation registered for that interface. If TFieldSurvey is going to implement its own interface (eg IFieldSurvey) you could do away with the names and then typecast back to ISurvey if needed.

    You could always use the [Inject] attribute too on _organization field rather than using .InjectField (after adding Global.Container.Common to your uses):

      TSurvey = class ( TInterfacedObject, ISurvey )
      private
        _id : Integer;
        [Inject]
        _organization : IOrganization;
    
        function GetId () : Integer;
        procedure SetId ( const value : Integer );
    
        function GetOrganization () : IOrganization;
        procedure SetOrganization ( const value : IOrganization);
    
      public
        property Id : Integer read GetId write SetId;
        property Organization: IOrganization read GetOrganization write SetOrganization;
      end;
    

    and your registration would be:

      GlobalContainer.RegisterType<TSurvey>.Implements<ISurvey>('SPRING_SURVEY');
      GlobalContainer.RegisterType<TFieldSurvey>.Implements<ISurvey>('SPRING_FIELD_SURVEY');