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";
}