Kennisbank

Dependency Injection in Delphi

Altijd al willen weten hoe je Dependency Injection met Delphi kunt gebruiken? Ik heb een voorbeeld voor je opgezet waarbij we in een paar stappen van sterk gekoppelde code naar mooie, losgekoppelde code gaan via Dependency Injection en met behulp van interfaces.

We starten met het volgende voorbeeld:

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.

Zoals je kunt zien is er in principe niets mis met de code. Het is valide Delphi code en het werkt. Echter, er zijn wel een paar kanttekeningen te plaatsen. Als eerste is de code sterk gekoppeld; de class TWordApp is niet alleen afhankelijk van de TLanguageTools class, maar je kunt de TLanguageTools class niet eens uitbreiden met extra functionaliteit. Ook al zou je de class TLanguageTools overriden met bijvoorbeeld TEnglishLanguageTools, dan nog gebruikt de TWordApp gewoon de base class TLanguageTools.

Dat kan beter! Een eerste flinke verbetering is door de afhankelijkheid (de dependency) uit de constructor Create te halen, en deze te injecteren. Een dependency injecteren kan op meerdere manieren, via properties, procedures of via constructors. In dit voorbeeld maak ik gebruik van de constructor. De onderstaande code heeft al een iets losser gekoppende afhankelijkheid tussen de twee 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.

Zoals je in het voorbeeld kunt zien maken we nog steeds gebruik van de TLanguageTools class. Echter, het is nu mogelijk om deze TLanguageTools class te overriden, en die nieuwe class mee te geven aan de TWordApp class. En eigenlijk is dit de basis van Dependency Injection. Je code is veel losser gekoppeld en beter herbruikbaar.

Door middel van interfaces kunnen we nog een stapje verder gaan. Het is namelijk mogelijk om de functionaliteit van de TLanguageTools class te beschrijven in verschillende interfaces, en deze door te geven aan de TWordApp class. Op deze manier programmeren we namelijk tegen abstracties, en niet tegen implementaties.

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.

Zoals je ziet is de TWordApp class nu helemaal niet meer afhankelijk van de TLanguageTools class. Zolang we aan de constructor een instantie met een implementatie van de interface ITranslator doorgeven zal de code gewoon werken. We hebben nu dus niet eens de class TLanguageTools meer nodig.

Dus, met behulp van Dependency Injection en met interfaces krijgen we mooie, losgekoppelde, en goed onderhoudbase code.

Geschreven door Marco Geuze
Directeur

Contact

Laat ons helpen jouw ambities concreet te maken.