Keyvan Akbary Seguir

Keyvan Akbary

Tech Lead en TransferWise

Autor del libro

Saber más

Patrón Composite

El patrón Composite es otro de los ya clásicos patrones de diseño presentados en el libro Gang of Four. Nos permite agrupar el comportamiento de colecciones de objetos en objetos individuales.

El patrón Composite permite componer objetos en estructuras árbol como medio para representar jerarquías parcialmente enteras. Facilita tratar con colecciones de objetos u objetos individuales de forma uniforme.

Sin contexto la definición queda difusa. Seguramente mejor presentarlo con un ejemplo práctico.

Imaginemos por un momento que somos responsables de diseñar un sencillo sistema bancario cuyo propósito es el de llevar las cuentas de transacciones monetarias.

Representamos una transacción como un valor, haciendo uso del patrón Value Object

class Transaction {
    private $value;

    public function __construct(int $value) {
        $this->value = $value;
    }

    public function value(): int {
        return $this->value;
    }
}

La cuenta bancaria es pues el concepto responsable de agrupar, enlazar y calcular el balance de las transacciones

class Account {
    private $transactions = [];

    public function link(Transaction $transaction): void {
        $this->transactions[] = $transaction;
    }

    public function balance(): int {
        $sum = 0;
        foreach ($this->transactions as $transaction) {
            $sum += $transaction->value();
        }

        return $sum;
    }
}
$account = new Account();
$account->link(new Transaction(5));
$account->link(new Transaction(10));

$balance = $account->balance();

En el mundo real es habitual que una persona tenga múltiples cuentas bancarias asociadas. Si quisieramos ofrecer el balance entre todas ellas, podríamos hacerlo introduciendo el concepto de cuenta general.

class OverallAccount {
    private $accounts = [];

    public function link(Account $account): void {
        $this->accounts[] = $account;
    }

    public function balance(): int {
        $sum = 0;
        foreach ($this->accounts as $account) {
            $sum += $account->balance();
        }

        return $sum;
    }
}
$account1 = new Account();
$account1->link(new Transaction(5));

$account2 = new Account();
$account2->link(new Transaction(10));

$overallAccount = new OverallAccount();
$overallAccount->link($account1);
$overallAccount->link($account2);

$balance = $overallAccount->balance();

Si nos detenemos un momento a repasar lo que acabamos de hacer es muy posible que te hayas percatado que, salvando algunas diferencias, la lógica de cálculo y enlazado de transacciones y cuentas es prácticamente la misma en los objetos de Account y OverallAccount. Ambos objetos son prácticamente idénticos y esto huele a duplicidad.

Eliminando duplicidad

Si la lógica para enlazar y calcular el balance de transacciones en cuentas a nivel individual es similar que al de grupos de cuentas, podemos eliminar la duplicidad haciendo uso del patrón Composite.

Definiendo un contrato común que represente un valor, del cual se pueda deducir un balance

interface Holding {
    public function balance(): int;
}

e implementando dicho contrato tanto en cuentas como transacciones

class Account implements Holding {
    private $holdings = [];

    public function link(Holding $holding): void {
        $this->holdings[] = $holding;
    }

    public function balance(): int {
        $sum = 0;
        foreach ($this->holdings as $holding) {
            $sum += $holding->balance();
        }

        return $sum;
    }
}
class Transaction implements Holding {
    public $value;

    public function __construct(int $value) {
        $this->value = $value;
    }

    public function balance(): int {
        return $this->value;
    }
}

ahora las cuentas puedan operar tanto con transacciones como con otras cuentas, haciendo posible estructuras recursivas. El objeto OverallAccount ya no es necesario y puede ser eliminado.

Aplicar Composite no solo elimina duplicidad, sino que también consigue librar al cliente de diferenciar entre cuentas individuales o colecciones de cuentas, unificando el comportamiento de ambos casos al provisto por Holding. Donde antes el código cliente esperaba un objeto individual ahora puede aceptar un compuesto.

$holding1 = new Account();
$holding1->link(new Transaction(5));

$holding2 = new Account();
$holding2->link(new Transaction(10));

$overallHolding = new Account();
$overallHolding->link($holding1);
$overallHolding->link($holding2);

$balance = $overallHolding->balance();

El paso a Composite es habitual en ciclos de refactoring como técnica para reducir duplicidad.

Conviene resaltar que aplicar el patrón Composite es un truco del programador, generalmente no apreciado como abstracción del mundo real. Sin embargo el beneficio de aplicarlo, en términos de reducción de complejidad y duplicidad en el código, es enorme y por lo general merece la pena.

Directorios que contienen directorios, suits tests que albergan suits tests, dibujos que agrupan dibujos; ninguno de estos casos tiene sentido en plano real pero todos ellos hacen el código mucho más sencillo.

Referencias

¿Ves algo raro? ¡Editame!

Copyright © 2022