Dependency Injection the mORMot way

Dependency Injection and Inversion of Control for part of the SOLID principles. That’s a lot of gobbledygook, but trust me, it’s a Good Thing™ It will help in keeping things separate and prevent the urge to create cross dependencies.

I haven’t seen any implementation of these patterns that doesn’t make use of interfaces. Interfaces are great as they enforce a contract between provider (whatever implements the interface) and consumer (the thing that uses the interface).

Real world example time:

Let’s say we have a service we want to be able to talk to without knowing the particulars of it’s implementation. For this example we’ll use an application settings provider. Application settings can be stored in many different formats, eg INI files, JSON files, Registry or even in a database. When reading these settings, we really shouldn’t care where these settings are stored.

The contract

The first step would be to declare the IApplicationSettings interface:

type
  IApplicationSettings = interface(IInvokable)
    ['{E1E76D15-B43A-4DF5-91CE-9CAC7AAAF789}']
    function getValue( aSetting : string; aDefault : string ) : string;
    function getValueInt( aSetting : string; aDefault : integer ) : integer;
  end;

This is just a basic example, but good enough to demonstrate the principles.
NB: Your interface needs to descend from IInvokable, or it won’t contain any RTTI. It also needs to be registered with mORMot. It makes sense to register the interface in the unit’s initialization section:

initialization
  TInterfaceFactory.RegisterInterfaces( [TypeInfo(IApplicationSettings)] );
end.

The service provider

A basic class that can provide what the contract demands.

Implementation of our interface:

type
  TINISettings = class( TInterfacedObject, IApplicationSettings )
  private
    fINIFile : TINIFile;
    fSection : string;
  public
    constructor Create( aSettigsFileName : TFileName; aSection : string );
    destructor Destroy; override;
    //Implement the interface
    function getValue( aSetting : string; aDefault : string ) : string;
    function getValueInt( aSetting : string; aDefault : integer ) : integer;
  end;
implementation

{ TINISettings }

constructor TINISettings.Create(aSettigsFileName: TFileName; aSection : string);
begin
  inherited Create;
  fSection := aSection;
  fINIFile := TIniFile.Create( aSettigsFileName );
end;

destructor TINISettings.Destroy;
begin
  fINIFile.Free;
  inherited;
end;

function TINISettings.getValue(aSetting, aDefault: string): string;
begin
  result := fINIFile.ReadString( fSection, aSetting, aDefault );
end;

function TINISettings.getValueInt(aSetting: string; aDefault: integer): integer;
begin
  result := fINIFile.ReadInteger( fSection, aSetting, aDefault );
end;

Simple and easy.

The magic

First we declare our class.

type
 TMyApplication = class(TInjectableObject)
 private
   FSettings: IApplicationSettings;
 public
   procedure PrintSomeSettings;
 published
   property Settings: IApplicationSettings read FSettings write FSettings;
 end;

The magic happens in as we descend from TInjectableObject. Any published interfaces will automagically be provided to our class.

This means that the following code will be executed without error and provide the expected output.

procedure TMyApplication.PrintSomeSettings;
begin
  writeln( 'Value of TestString: ', Settings.getValue('TestString', 'Not found' ) );
end;

All that’s left is to create our class, and call our test method

procedure TestIt;
var
  MyApplication : TMyApplication;
begin
  MyApplication := TMyApplication.CreateInjected( [],[], [TINISettings.Create( ChangeFileExt(ParamStr(0), '.INI'), 'Test' )]);
  MyApplication.PrintSomeSettings;
end;

The first two empty arrays are for stubs and other resolvers respectively, none of which we need in this example.

Why bother

As Stefan Glienke pointed out, in a previous version of this post, we were implementing the Service Locator pattern, which is considered an anti-pattern by many. It’s very close to Dependency Injection, but tends to hide the class’s dependencies. It also breaks the “Tell, Don’t Ask” principle.