Rolling your own Dependency Injection, Part two

In Part 1 we covered the beginnings of rolling your own Dependency Injection framework as an alternative to using the Delphi Spring Framework. We discussed the patterns and objects involved and described using RTTI to perform the actual injection. Now we’ll look at an example of using DI to inject logging functionality into an object.

So, here is what DI looks like in action. Let’s say you build a car and all the salient action on that car, including construction, is logged in a logger. In Listing 1, ILogger is an interface to some event logger like CodeSite, SmartInspect or maybe just writes lines to a text file. ICar uses an instance of ILogger on the inside to record vehicle events.

Listing 1

type
ILogger = interface
    [bla bla bla]
    procedure WriteLn( const Line: string);
end;

ICar = interface
[bla bla bla]
    procedure Drive;
    procedure TurnLeft;
    procedure RightRight;
... etc ...
end;

Logger
We might implement ICar something like listing 2. The [Injection] attribute marks the data member for injection. We will see what this means soon.
Listing 2

type
TCar = class( TInterfacedObject, ICar)
  private
    [Injection] FLogger: ILogger;

    procedure Drive;
    procedure TurnLeft;
    procedure RightRight;

  public
    constructor Create;
  end;

Ordinarily, at some higher level, we might use a car thus defined, as in listing 3.
Listing 3

procedure MakeAndDriveCar;
// Uses unit in which TCar is defined.
var
  MyCar: ICar;
begin
MyCar := TCar.Create;
MyCar.Drive;
MyCar.TurnLeft;
.. etc
end;

With DI, we do something slightly different (and already better), as in listing 4.
Listing 4

procedure MakeAndDriveCar;
// Does not require visibility to TCar.
var
  MyCar: ICar;
begin
MyCar := ServiceProvider.Acquire( ICar); // ServiceProvider is an "abstract factory" pattern.
MyCar.Drive;
MyCar.TurnLeft;
.. etc
end;

Basic stuff right? Now for the magic. List 4 show the constructor for TCar.
Listing 5

constructor TCar.Create;
begin
FLogger.WriteLn( 'This car is created.');
end;

Whoah! Does it look like an AV right there on the first line of the constructor? Actually, this code works and works well thanks to DI. By the time of entry into the constructor, dependent data members and properties are already connected to the appropriate service. The car’s interconnection with the outside world is no longer a conceptual burdon on the Car. DI gives us the optimal level of object isolation and simplification.
Connected
Compare with the typical alternative constructors that we were used to before DI, in listing 6

Listing 6

constructor TCar.Create;
begin
FLogger := TMyLogger.Create( params); // Requires visibility to and dependency on TMyLogger
FLogger.WriteLn( 'This car is created.');
end;
... or maybe ....
constructor TCar.Create( const Logger1: ILogger);
begin
FLogger := Logger1; // Not so bad, but fattens up TCar's interface width.
FLogger.WriteLn( 'This car is created.');
end;

Further Reading

Dependency Injection
View Nick Hodge’s excellent presentation on Dependency Injection and Spring Framework from CodeRage 6. Nick’s very popular blog includes many entries on DI.

As for rolling your own, you can light-weight DI demo here. Listing 6 shows the public interface to it.
Listing 6

IServiceProvider = interface;
TServiceFactory = reference to function( Obj: TObject; const Config: string;
  const ServiceProvider: IServiceProvider): IInterface;
IServiceProvider = interface
  ['{1C46BAEA-B723-4E07-B1A1-1F1E286AC421}']
    function  RegisterLiveService   ( const ServiceIID: TGUID; const Service: IInterface; const Config: string=''): integer;
    // ^-- Register an already-created-object as a service.
    function  RegisterFlyweightService( const ServiceIID: TGUID; CreateBase: TClass; Factory: TServiceFactory; const Config: string = ''): integer;
    // ^-- Register a service to be created on demand by a flyweight abstract factory.
    function  RegisterServiceClass( Registrant: TClass; const ServiceIID: TGUID): TList;
    // ^-- Register a service to be created on demand with constructors decorated with [Configuration].
    procedure DeregisterService( RegistrationCookie: integer);
    function  Acquire( const ServiceIID: TGUID; out Intf; const Config: string = ''): boolean;
    // ^-- Acquire a service, with Dependency Injection.
    procedure ShutDown;
  end;
Posted in Coding Techniques, RTL
3 comments on “Rolling your own Dependency Injection, Part two
  1. Pierre Y. says:

    The code don’t compile with Delphi XE2 (Update 3)

    I think this is due to :
    function Acquire( const ServiceIID: TGUID; out Intf; const Config: string = ”):

    Because this function is declared in an interface.

    • Pierre Y. says:

      I changed the declaration to “out Intf: IInterface” and it works, but at user level it becomes ugly because you have to cast the out parameter to IInterface explicitly.

      procedure TmfmDIYDI.actCreateCarExecute(Sender: TObject);
      begin
      if not Assigned(FCar) then
      begin
      FServiceProvider.Acquire( ICar, IInterface(FCar));
      FCar.TurnLeft;
      FCar.TurnRight;
      end
      end;

      • Pierre Y. says:

        I think I find a better answer. When converted your Delphi 2010 project files, Delphi activated the option to explicitely emit runtime type information. I disabled the option and your code compile as-is now.