Publicación: about 1 month

Clases de tipo proveedor (providers)


Qué son

Son clases encargadas de configurar y registrar otras clases, generalmente de tipo servicio. Suelen servir como puntos centrales de acceso y/o inicialización para clases que comporten un mismo propósito. Si nos queremos ver un poco más técnicos, estamos implementando el patrón de diseño Provider Pattern.

Cuando utilizarlas

  • Cuando se requiere encapsular clases con un comportamiento similar pero una implementación diferente. Ej: diferentes estrategias de envío (correo, SMS, push).

  • Cuando necesitamos centralizar el acceso a clases que cumplen un mismo contrato. Ej: un proveedor de métodos de autenticación (OAuth, JWT, LDAP).

  • Cuando es necesario intercambiar dinámicamente entre diferentes implementaciones. Ej: elegir un procesador de pagos (Stripe, PayPal) en tiempo de ejecución.

  • Cuando existen múltiples condicionales que aplican la misma lógica con pequeñas variantes. Ej: if, else o switch que dependen del tipo de operación; se pueden reemplazar por un proveedor que resuelva la implementación correcta.

Cómo utilizarlas

Supongamos que tenemos tres clases que comparten un mismo comportamiento: efectuar un movimiento bancario. 

Cada clase representa una operación distinta:

  • Depósito: realiza un ingreso de dinero en una cuenta bancaria.
  • Retiro: permite extraer dinero de una cuenta.
  • Transferencia: ejecuta el envío de dinero desde una cuenta hacia otra.

class Deposito {
    public function ejecutar(float $monto): string {
        return "Depósito de $" . number_format($monto, 2);
    }
}

class Retiro {
    public function ejecutar(float $monto): string {
        return "Retiro de $" . number_format($monto, 2);
    }
}

class Transferencia {
    public function ejecutar(float $monto): string {
        return "Transferencia de $" . number_format($monto, 2);
    }
}

Normalmente, utilizaríamos condicionales para acceder a ellas.
if(type === ‘deposito’) return new Deposito()
if(type === ‘retiro’) return new Retiro()
if(type === ‘transferencia’) return new Transferencia()

Sin embargo, con el tiempo seguro irán incrementando los tipos. Por lo que, un provider puede ser una buena opción para mantener el código escalable y fácil de utilizar.

1. Crear una interfaz (no es necesaria a menos que el lenguaje te lo permita)
interface TransaccionInterface {
    public function ejecutar(float $monto): string;
}

2. Implementar la interfaz en nuestras clases
class Deposito implements TransaccionInterface {
. . .

class Retiro implements TransaccionInterface {
. . .

class Transferencia implements TransaccionInterface {
. . .

3. Crear nuestro provider
class TransaccionProvider {
    private array $transacciones = [];

    public function registrar(string $tipo, TransaccionInterface $transaccion): void {
        $this->transacciones[$tipo] = $transaccion;
    }

    public function obtener(string $tipo): ?TransaccionInterface {
        return $this->transacciones[$tipo] ?? null;
    }

    public function tiposDisponibles(): array {
        return array_keys($this->transacciones);
    }
}

Finalmente, creamos un objeto de tipo provider, registramos todas las clases que implementen la interfaz TransaccionInterface y les asociamos un identificar para fácil acceso.
$provider = new TransaccionProvider();

// Registrar los tipos de transacciones
$provider->registrar('deposito', new Deposito());
$provider->registrar('retiro', new Retiro());
$provider->registrar('transferencia', new Transferencia());

// Ejecutar una transacción específica
echo $provider->obtener('deposito')->ejecutar(1500) . "\n";       // Depósito de $1,500.00
echo $provider->obtener('retiro')->ejecutar(500) . "\n";          // Retiro de $500.00
echo $provider->obtener('transferencia')->ejecutar(300) . "\n";   // Transferencia de $300.00

// Iterar sobre todos los tipos disponibles
foreach ($provider->tiposDisponibles() as $tipo) {
    $transaccion = $provider->obtener($tipo);
    echo strtoupper($tipo) . ": " . $transaccion->ejecutar(100) . "\n";
}