Dépendance circulaire.
Une dépendance circulaire est un antipatron où plusieurs modules dépendent les uns des autres, si bien qu’aucun ne peut être compilé, testé ou réutilisé seul.
En génie logiciel, une dépendance circulaire (ou dépendance cyclique) est une relation entre deux modules ou plus qui dépendent l'un de l'autre, directement ou indirectement, pour fonctionner correctement. De tels modules sont aussi qualifiés de mutuellement récursifs.
Vue d'ensemble
Les dépendances circulaires sont naturelles dans de nombreux modèles de domaine où certains objets d'un même domaine dépendent les uns des autres. Cependant, en conception logicielle, les dépendances circulaires entre des modules logiciels plus importants sont considérées comme un anti-pattern en raison de leurs effets négatifs. Malgré cela, de telles dépendances circulaires (ou cycliques) se sont révélées répandues parmi les fichiers source des logiciels du monde réel. Les modules mutuellement récursifs sont toutefois assez courants en programmation fonctionnelle, où les définitions inductives et récursives sont souvent encouragées.
Problèmes
Les dépendances circulaires peuvent provoquer de nombreux effets indésirables dans les programmes informatiques. Le plus problématique du point de vue de la conception logicielle est le couplage fort (tight coupling) des modules mutuellement dépendants, qui réduit ou rend impossible la réutilisation séparée d'un module unique.
Les dépendances circulaires peuvent provoquer un effet domino lorsqu'un petit changement local dans un module se propage à d'autres modules et a des effets globaux indésirables (erreurs de programme, erreurs de compilation). Les dépendances circulaires peuvent aussi entraîner des récursions infinies ou d'autres défaillances inattendues.
Les dépendances circulaires peuvent également causer des fuites de mémoire en empêchant certains ramasse-miettes automatiques (ceux qui utilisent le comptage de références) de désallouer les objets inutilisés.
Causes et solutions
Dans de très vastes conceptions logicielles, les ingénieurs logiciels peuvent perdre le contexte et introduire par inadvertance des dépendances circulaires. Il existe des outils pour analyser le logiciel et trouver les dépendances circulaires indésirables.
Des dépendances circulaires peuvent être introduites lors de l'implémentation d'une fonctionnalité de rappel (callback). Cela peut être évité en appliquant des patrons de conception comme le patron observateur.
Exemple
OrderService a besoin de Billing pour facturer une commande, et Billing rappelle OrderService pour la marquer comme payée. Aucune des deux classes ne peut être compilée, testée ou réutilisée sans l’autre.
class OrderService {
private final Billing billing = new Billing(this);
void place(Order order) { billing.charge(order); }
void markPaid(Order order) { order.paid = true; }
}
class Billing {
private final OrderService orders;
Billing(OrderService orders) { this.orders = orders; }
void charge(Order order) {
// ... débiter la carte ...
orders.markPaid(order); // Billing rappelle OrderService
}
}
Le rappel passe par une petite interface qui appartient au module de facturation, comme dans le patron Observateur :
interface PaymentListener {
void paid(Order order);
}
class Billing {
private final PaymentListener listener;
Billing(PaymentListener listener) { this.listener = listener; }
void charge(Order order) {
// ... débiter la carte ...
listener.paid(order);
}
}
class OrderService implements PaymentListener {
private final Billing billing = new Billing(this);
void place(Order order) { billing.charge(order); }
@Override
public void paid(Order order) { order.paid = true; }
}
Billing ne dépend plus que de PaymentListener : la dépendance entre les modules ne va plus que dans un sens, commandes → facturation.
Adapté de l’article Wikipédia “Circular dependency” (révision 1303252600), disponible sous licence CC BY-SA 4.0 ; le texte a été traduit et modifié, et des exemples ont été ajoutés.