Knowledge Base

Dependency Injection in Delphi

Always wanted to know how to use Dependency Injection with Delphi? I’ve set up an example for you in which we go from strongly coupled code to beautiful, uncoupled code in a few steps via Dependency Injection and with the help of interfaces.

We’ll start with the following example:

unit DI1;

interface

type
  TLanguageTools = class
  private
    procedure CheckGrammar;
    procedure Translate;
  end;

  TWordApp = class
  private
    FLanguageTools: TLanguageTools;
  public
    constructor Create;
  end;


implementation

constructor TWordApp.Create;
begin
  FLanguageTools := TLanguageTools.Create;

  FLanguageTools.Translate;
end;

{ TLanguageTools }

procedure TLanguageTools.CheckGrammar;
begin
  //
end;

procedure TLanguageTools.Translate;
begin
  //
end;

end.

As you can see, there is nothing wrong with the code in principle. It is valid Delphi code and it works. However, there are a few remarks to be made. First of all, the code is strongly coupled; the TWordApp class not only depends on the TLanguageTools class, but you cannot even extend the TLanguageTools class with extra functionality. Even if you would override the TLanguageTools class with, for example, TEnglishLanguageTools, TWordApp simply uses the base class TLanguageTools.

We can do better than that! A first major improvement is to remove the dependency from the constructor Create, and inject it. Injecting a dependency can be done in several ways, via properties, procedures or constructors. In this example, I use the constructor. The code below already has a slightly looser dependency between the two classes:

unit DI2;

interface

type
  TLanguageTools = class
  private
    procedure CheckGrammar;
    procedure Translate;
  end;

  TWordApp = class
  private
    FLanguageTools: TLanguageTools;
  public
    constructor Create(ALanguageTools: TLanguageTools);
  end;


implementation

constructor TWordApp.Create(ALanguageTools: TLanguageTools);
begin
  FLanguageTools := ALanguageTools;

  FLanguageTools.Translate;
end;


{ TLanguageTools }

procedure TLanguageTools.CheckGrammar;
begin
  //
end;

procedure TLanguageTools.Translate;
begin
  //
end;

end.

As you can see in the example, we are still using the TLanguageTools class. However, it is now possible to override this TLanguageTools class, and give this new class to the TWordApp class. And actually, this is the basis of Dependency Injection. Your code is much more loosely coupled and reusable.

By using interfaces, we can go one step further. It is possible to describe the functionality of the TLanguageTools class in different interfaces, and pass this on to the TWordApp class. In this way, we program against abstractions, and not against implementations.

unit DI3;

interface

type
  IGrammarChecker = interface
    ['{FED7BA76-0EDE-40E7-BABB-16BCFE76F6DF}']
    procedure CheckGrammar;
  end;

  ITranslator = interface
    ['{C1F1092F-3589-49E5-8F22-33E8D7587A8B}']
    procedure Translate;
  end;


  TLanguageTools = class(TInterfacedObject, IGrammarChecker, ITranslator)
  private
    procedure CheckGrammar;
    procedure Translate;
  end;

  TWordApp = class
  private
    FTranslator: ITranslator;
  public
    constructor Create(ATranslator: ITranslator);
  end;

implementation

constructor TWordApp.Create(ATranslator: ITranslator);
begin
  FTranslator := ATranslator;

  FTranslator.Translate;
end;

procedure TLanguageTools.CheckGrammar;
begin
  //
end;

procedure TLanguageTools.Translate;
begin
  //
end;

end.

As you can see, the TWordApp class is no longer at all dependent on the TLanguageTools class. As long as we pass on to the constructor an instance with an implementation of the ITranslator interface, the code will just work. We don’t even need the TLanguageTools class anymore.

So, with the help of Dependency Injection and with interfaces, we get nice, decoupled, and well-maintained sourcecode.

Written by Marco Geuze
Director

Contact

Let us help you to realise your ambitions