Dépendances mutuelles et références circulaires des unités

| johann17 | chris37 |

Parfois, on est amené à faire deux unités où chacune fait référence aux fonctions de l'autre. Il s'agit d'une dépendance mutuelle.
Dans certains cas, cela peut poser des difficultés.

Commentez

- Titre : Dépendances mutuelles et références circulaires des unités

- Auteurs : johann17, chris37

- Parution : 15 mars 2009

- Mise à jour : 1 janvier 2020

- Licence : Copyright ® 2009-2010 johann17. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Dépendance au niveau implémentation

- Dépendance mixte interface-implémentation

- Référence circulaire ou dépendance interface-interface

- Fausse référence circulaire et vraie référence circulaire

- Solution par la fusion

- Solution par le non-typage

Dépendance au niveau implémentation

Pour commencer, il y a un cas où cela marche sans problème, c'est quand la clause uses qui fait référence à l'autre unité se trouve dans la section d'implémentation. Dans ce cas, l'ensemble des déclarations de types, de procédures et de fonctions sont lues dans chacune des deux unités. En effet, ces déclarations se trouvent dans la section interface, c'est-à-dire avant que la question de la dépendance mutuelle soit soulevée. Quand, dans l'implémentation, on fait référence à l'autre unité, tous les membres publics sont déjà définis.

Dépendance mixte interface-implémentation

Il se peut qu'une unité A fasse référence à une unité B par une clause uses dans sa partie implémentation, tandis que l'unité B fasse référence l'unité A dans sa partie interface. Dans ce cas, le compilateur va d'abord lire l'interface de A, parce qu'elle est nécessaire à l'interprétation de l'interface de l'unité B. Par exemple, l'unité B peut proposer des fonctions qui prennent en paramètre un type défini dans l'unité A. Une fois l'interface de B déterminée, le compilateur va s'occuper des implémentations. Par la suite, l'unité A et l'unité B peuvent utiliser dans leur implémentation les fonctions de l'autre.

Référence circulaire ou dépendance interface-interface

Maintenant, si l'on fait deux unités qui font référence l'une à l'autre, mais avec une clause uses dans leurs interfaces, on obtient l'erreur de référence circulaire. En effet, pour pouvoir interpréter correctement l'interface d'une unité, il faut que le compilateur ait lu l'interface de l'autre unité, et vice-versa. Certains compilateurs ne génèrent pas d'erreur dans ces cas-là, mais en Pascal, pour le moment, une telle chose est impossible.

Fausse référence circulaire et vraie référence circulaire

Il se peut qu'en réalité, il ne soit pas nécessaire de déclarer la dépendance au niveau de l'interface de l'unité. Dans ce cas, il suffit de déplacer une partie de la clause uses dans la partie implémentation, pour les unités ne faisant appel seulement à ce moment à des procédures, des fonctions, ou à des types déclarés dans d'autres unités. C'est le cas le plus fréquent fort heureusement.

Sinon, c'est généralement que certains objets définis dans une unité A contiennent des champs ou des méthodes utilisant des objets définis dans une unité B, qui eux-mêmes contiennent des champs ou des méthodes utilisant des objets définis dans l'unité A.
Par exemple :

{ dans l'unité A }
type
TObjetA = class
function DonneObjetB: TObjetB;
end;

{ dans l'unité B }
type
TObjetB = class
function DonneObjetA: TObjetA;
end;

Solution par la fusion

On peut régler le problème de la référence circulaire en mettant les deux objets dans une seule et même unité et en prédéclarant les types.
Cela se fait de la manière suivante :

{ dans l'unité fusionnée AB }
type
TObjetA = class; { prédéclaration de A }
TObjetB = class; { prédéclaration de B }

TObjetA = class  { déclaration complète de A }
function DonneObjetB: TObjetB;
end;

TObjetB = class  { déclaration complète de B }
function DonneObjetA: TObjetA;
end;

Bien entendu, il se peut que, de fil en aiguille, on obtienne des fichiers de code très gros, ce qui est l'inconvénient de cette méthode.

Solution par le non-typage

Enfin, on peut résoudre le problème en ne typant pas les champs ou les paramètres et les valeurs de retour des procédures et des fonctions.

{ dans l'unité A }
type
TObjetA = class
function DonneObjetB: TObject;
end;

{ dans l'unité B }
type
TObjetB = class
function DonneObjetA: TObject;
end;

Bien que la solution ne soit pas très élégante, elle permet de contourner le problème. Lors de l'appel des fonctions DonneObjetA et DonneObjetB, il faudra faire un transtypage pour avoir un objet bel et bien identifié comme étant de type TObjetA ou TObjetB.
Par exemple :

procedure UtiliseObjetA;
var objA : TObjetA;
begin
objA := TObjetA(objB.DonneObjetA);
objA.Affiche;
end;