Injection de dépendances #1

Qu’est ce que l’injection de dépendances?

L’injection de dépendances permet à des classes de ne pas se soucier de l’instanciation des classes dont elles dépendent. Il s’agit d’un principe qui permet de mettre en place l’inversion de contrôle.

Le but est de découpler son code, le rendre plus clair de sorte à avoir un source plus lisible et plus facile à maintenir.

Il arrive souvent d’avoir une classe A qui dépend d’une classe B. La première classe va alors créer un objet de cette classe.

Avec ce principe d’injection de dépendances, une méthode va créer un objet de type A, un objet de type B et elle va transmettre cet instance à la classe A.

Exemple

Ci-dessous l’exemple d’une personne avec un panier et des légumes. Le code est réalisé avec une version XE7 de RAD Studio.

type
  TLegume = class
     function GetNom: string;
  end;
 
  TPanier = class
     procedure Ajouter(aLegume: TLegume);
  end;
 
  TPersonne = class
  strict private
     FPanier: TPanier;
  public
     constructor Create;
     destructor Destroy; override;
     procedure AjouterDansPanier(aLegume: TLegume);
  end;

Ensuite les déclarations des méthodes. Le légume va retourner un nom et le panier va simplement afficher comme quoi le légume est ajouté.

Pour la personne c’est différent, le constructeur va créer un panier et le destructeur va le libérer. La méthode AjouterDansPanier va appeler la méthode Ajouter du TPanier.

{ TPersonne }
 
procedure TPersonne.AjouterDansPanier(aLegume: TLegume);
begin
  FPanier.Ajouter(aLegume);
end;
 
constructor TPersonne.Create;
begin
  FPanier := TPanier.Create;
end;
 
destructor TPersonne.Destroy;
begin
  FPanier.Free;
  inherited;
end;
 
{ TPanier }
 
procedure TPanier.Ajouter(aLegume: TLegume);
begin
  Writeln(Format('%s dans le panier.', [aLegume.GetNom]));
end;
 
{ TLegume }
 
function TLegume.GetNom: string;
begin
  Result := 'Un légume';
end;

Pour terminer nous allons ajouter une méthode RemplirPanier. Lors de la création de la personne, le panier est créé.

procedure RemplirPanier;
var
  Personne: TPersonne;
  Legume: TLegume;
begin
  Personne := TPersonne.Create;
  try
    Legume := TLegume.Create;
    try
      Personne.AjouterDansPanier(Legume);
    finally
      Legume.Free;
    end;
  finally
    Personne.Free;
  end;
end;

Injection de constructeur

L’injection de constructeur est le fait de passer une instance valide d’un objet au Create. Cela signifie que cette dépendance est obligatoire. Pour cela le constructeur de la classe TPersonne est modifié. Un contrôle est ajouté pour vérifier que panier est valide.

constructor TPersonne.Create(aPanier: TPanier);
begin
  if aPanier = nil then
    raise Exception.Create('La personne doit avoir un panier.');
 
  FPanier := aPanier;
end;

Désormais le panier doit être créé avant la personne. La méthode pour le remplir est modifiée.

procedure RemplirPanier;
var
  Personne: TPersonne;
  Legume: TLegume;
  Panier: TPanier;
begin
  Personne := TPersonne.Create(TPanier.Create);
  try
    Legume := TLegume.Create;
    try
      Personne.AjouterDansPanier(Legume);
    finally
      Legume.Free;
    end;
  finally
    Personne.Free;
  end;
end;

Injection de setter

L’injection de setter est utilisée quand la classe requise n’est pas obligatoire. Si le panier avait été facultatif il aurait fallu créer une propriété sur la personne.

  TPersonne = class
  strict private
     FPanier: TPanier;
  public
     constructor Create;
     destructor Destroy; override;
     procedure AjouterDansPanier(aLegume: TLegume);
     property Panier: TPanier read FPanier write FPanier;
  end;

Injection de méthode

L’injection de méthode permet de garder un contrôle sur la donnée. Elle peut venir en complément de l’injection de setter. La propriété est alors en lecture seule.

  TPersonne = class
  strict private
     FPanier: TPanier;
  public
     constructor Create;
     destructor Destroy; override;
     procedure AjouterDansPanier(aLegume: TLegume);
 
     procedure ModifierPanier(aPanier: TPanier);
     property Panier: TPanier read FPanier;
  end;

Utiliser les interfaces

Les interfaces permettent simplement de déclarer publiquement des méthodes sur un objet. L’un des avantages est la durée de vie des interfaces qui est cloisonnée à la portée du code, généralement une méthode.

En reprenant l’exemple précédent nous allons créer une interface pour le légume et une pour le panier. Le destructeur de la classe TPersonne n’est plus nécessaire.

type
  ILegume = interface
  ['{3B356366-27C9-4C4E-BF6D-FAF449D3F8C6}']
    function GetNom: string;
  end;
 
  TLegume = class(TInterfacedObject, ILegume)
    function GetNom: string;
  end;
 
  IPanier = interface
  ['{1514BE1C-03A5-42E7-BF49-C1A385BBEFF3}']
    procedure Ajouter(aLegume: ILegume);
  end;
 
  TPanier = class(TInterfacedObject, IPanier)
    procedure Ajouter(aLegume: ILegume);
  end;
 
  TPersonne = class
  strict private
    FPanier: IPanier;
  public
    constructor Create(aPanier: IPanier);
    procedure AjouterDansPanier(aLegume: ILegume);
  end;

La méthode pour remplir le panier est simplifiée du fait de l’utilisation des interfaces.

procedure RemplirPanier;
var
  Personne: TPersonne;
  Legume: ILegume;
begin
  Personne := TPersonne.Create(TPanier.Create);
  try
    Legume := TLegume.Create;
    Personne.AjouterDansPanier(Legume);
  finally
    Personne.Free;
  end;
end;

Conclusion

L’inversion de contrôle est ainsi illustré dans cet exemple, les dépendances entre les objets on été découplées. La personne ne va pas créer un panier, et ce dernier ne va pas lui même créer un légume. De cet fait une classe fonctionne uniquement avec ce dont elle a besoin, si l’interface donnant le nom du légume suffit il n’y a aucune raison de lui donner l’intégralité de la classe.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

*