Logotipo de ConstructiCat
CodeBust.
Explorar la sección ▾

Dependencia circular.

Una dependencia circular es un antipatrón en el que varios módulos dependen entre sí y ninguno puede compilarse, probarse ni reutilizarse por separado.

En ingeniería de software, una dependencia circular (o dependencia cíclica) es una relación entre dos o más módulos que, directa o indirectamente, dependen unos de otros para funcionar correctamente. Dichos módulos también se conocen como mutuamente recursivos.

Visión general

Las dependencias circulares son naturales en muchos modelos de dominio donde ciertos objetos del mismo dominio dependen entre sí. Sin embargo, en el diseño de software, las dependencias circulares entre módulos de software más grandes se consideran un antipatrón por sus efectos negativos. A pesar de ello, se ha comprobado que estas dependencias circulares (o cíclicas) están muy extendidas entre los archivos fuente del software del mundo real. Los módulos mutuamente recursivos son, no obstante, algo comunes en la programación funcional, donde a menudo se fomentan las definiciones inductivas y recursivas.

Problemas

Las dependencias circulares pueden provocar muchos efectos no deseados en los programas de software. Lo más problemático desde el punto de vista del diseño de software es el fuerte acoplamiento de los módulos mutuamente dependientes, que reduce o imposibilita la reutilización por separado de un solo módulo.

Las dependencias circulares pueden causar un efecto dominó cuando un pequeño cambio local en un módulo se propaga a otros módulos y tiene efectos globales no deseados (errores de programa, errores de compilación). Las dependencias circulares también pueden dar lugar a recursiones infinitas u otros fallos inesperados.

Las dependencias circulares también pueden provocar fugas de memoria al impedir que ciertos recolectores de basura automáticos (los que usan conteo de referencias) liberen objetos no utilizados.

Causas y soluciones

En diseños de software muy grandes, los ingenieros de software pueden perder el contexto e introducir dependencias circulares de forma inadvertida. Existen herramientas para analizar el software y encontrar dependencias circulares no deseadas.

Las dependencias circulares pueden introducirse al implementar la funcionalidad de callback. Esto puede evitarse aplicando patrones de diseño como el patrón observador.

Ejemplo

OrderService necesita a Billing para cobrar un pedido, y Billing vuelve a llamar a OrderService para marcarlo como pagado. Ninguna de las dos clases puede compilarse, probarse ni reutilizarse sin la otra.

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) {
        // ... cobrar la tarjeta ...
        orders.markPaid(order); // Billing vuelve a llamar a OrderService
    }
}

La llamada de retorno pasa por una pequeña interfaz que pertenece al módulo de facturación, como en el patrón Observer:

interface PaymentListener {
    void paid(Order order);
}

class Billing {
    private final PaymentListener listener;

    Billing(PaymentListener listener) { this.listener = listener; }

    void charge(Order order) {
        // ... cobrar la tarjeta ...
        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; }
}

Ahora Billing solo depende de PaymentListener, así que la dependencia entre los módulos va en un único sentido: pedidos → facturación.

Adaptado del artículo de Wikipedia “Circular dependency” (revisión 1303252600), disponible bajo la licencia CC BY-SA 4.0; el texto ha sido traducido y editado, y se han añadido ejemplos.