Programmation Pascal/Dépendances mutuelles et références circulaires
Dépendances mutuelles et références circulaires
modifierParfois, on est amené à faire deux unités où chacune fait références aux fonctions de l'autre. Il s'agit d'une dépendance mutuelle. Dans certains cas, cela peut poser des difficultés.
Dépendance au niveau implémentation
modifierPour 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éclaration se trouve 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
modifierIl se peut qu'une unité A fasse référence à une unité B par une cause 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 leur interface, 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ère 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
modifierIl 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ême 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
modifierOn 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 obtiennent des fichiers de code très gros, ce qui est l'inconvénient de cette méthode.
Solution par le non-typage
modifierEnfin, 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;