Patrón Factory en PHP: Factory Method y Abstract Factory con ejemplos reales

El patrón Factory centraliza la creación de objetos en un único lugar, eliminando la dependencia directa de new dispersada por todo el código. PHP permite implementarlo de dos formas: el Factory Method, donde una clase define el método de creación y las subclases deciden qué objeto concreto instanciar, y el Abstract Factory, que agrupa la creación de familias de objetos relacionados. Ambos mejoran la mantenibilidad y facilitan los tests.

Factory Method: centralizar la creación

El Factory Method define un método en la clase base que devuelve un objeto. Las subclases sobrescriben ese método para devolver una instancia concreta diferente. El código cliente trabaja con la interfaz, sin saber qué clase concreta recibe.

<?php
interface Notificacion
{
    public function enviar(string $destino, string $mensaje): bool;
}

class NotificacionEmail implements Notificacion
{
    public function enviar(string $destino, string $mensaje): bool
    {
        // lógica de envío de email
        echo "Email a $destino: $mensajen";
        return true;
    }
}

class NotificacionSMS implements Notificacion
{
    public function enviar(string $destino, string $mensaje): bool
    {
        // lógica de envío de SMS
        echo "SMS a $destino: $mensajen";
        return true;
    }
}

class NotificacionPush implements Notificacion
{
    public function enviar(string $destino, string $mensaje): bool
    {
        echo "Push a $destino: $mensajen";
        return true;
    }
}

// Factory que centraliza la creación
class NotificacionFactory
{
    public static function crear(string $tipo): Notificacion
    {
        return match($tipo) {
            'email' => new NotificacionEmail(),
            'sms'   => new NotificacionSMS(),
            'push'  => new NotificacionPush(),
            default => throw new InvalidArgumentException("Tipo no soportado: $tipo"),
        };
    }
}

// El código cliente no sabe qué clase concreta usa
$canal = $_POST['canal'] ?? 'email';
$notif = NotificacionFactory::crear($canal);
$notif->enviar('[email protected]', '¡Tu pedido ha salido!');
?>

Abstract Factory: familias de objetos

Cuando hay varias familias de objetos relacionados (por ejemplo, distintas pasarelas de pago, cada una con su cliente, su logger y su validador), el Abstract Factory agrupa la creación de toda la familia.

<?php
// Interfaces de productos
interface ClientePago
{
    public function cobrar(float $importe, string $moneda): bool;
}

interface ValidadorPago
{
    public function validar(array $datos): bool;
}

// Familia Stripe
class StripeCliente implements ClientePago
{
    public function cobrar(float $importe, string $moneda): bool
    {
        echo "Stripe: cobrando $importe $monedan";
        return true;
    }
}

class StripeValidador implements ValidadorPago
{
    public function validar(array $datos): bool
    {
        return isset($datos['stripe_token']);
    }
}

// Familia PayPal
class PayPalCliente implements ClientePago
{
    public function cobrar(float $importe, string $moneda): bool
    {
        echo "PayPal: cobrando $importe $monedan";
        return true;
    }
}

class PayPalValidador implements ValidadorPago
{
    public function validar(array $datos): bool
    {
        return isset($datos['paypal_order_id']);
    }
}

// Abstract Factory
interface PasarelaPagoFactory
{
    public function crearCliente(): ClientePago;
    public function crearValidador(): ValidadorPago;
}

class StripeFactory implements PasarelaPagoFactory
{
    public function crearCliente(): ClientePago    { return new StripeCliente(); }
    public function crearValidador(): ValidadorPago { return new StripeValidador(); }
}

class PayPalFactory implements PasarelaPagoFactory
{
    public function crearCliente(): ClientePago    { return new PayPalCliente(); }
    public function crearValidador(): ValidadorPago { return new PayPalValidador(); }
}

// Servicio de pago independiente de la pasarela concreta
class ServicioPago
{
    private ClientePago    $cliente;
    private ValidadorPago  $validador;

    public function __construct(PasarelaPagoFactory $factory)
    {
        $this->cliente   = $factory->crearCliente();
        $this->validador = $factory->crearValidador();
    }

    public function procesar(array $datos, float $importe): bool
    {
        if (!$this->validador->validar($datos)) {
            throw new InvalidArgumentException('Datos de pago no válidos.');
        }
        return $this->cliente->cobrar($importe, 'EUR');
    }
}

// Seleccionar la pasarela según configuración
$pasarela = $_ENV['PASARELA_PAGO'] ?? 'stripe';
$factory  = match($pasarela) {
    'stripe' => new StripeFactory(),
    'paypal' => new PayPalFactory(),
    default  => throw new RuntimeException("Pasarela desconocida: $pasarela"),
};

$servicio = new ServicioPago($factory);
$servicio->procesar($_POST, 49.99);
?>

Por qué evitar el new directo

Usar new directamente en el código de negocio acopla la clase a una implementación concreta. Si el constructor cambia de firma o se quiere sustituir la clase en tests, hay que modificar todos los puntos donde aparece new. Una factory centraliza ese acoplamiento en un único lugar.

<?php
// Acoplado: difícil de testear y cambiar
class ServicioNotificaciones
{
    private NotificacionEmail $email; // acoplado a Email

    public function __construct()
    {
        $this->email = new NotificacionEmail(); // new directo
    }
}

// Desacoplado: fácil de testear con un mock
class ServicioNotificacionesV2
{
    public function __construct(
        private readonly Notificacion $canal  // recibe la interfaz
    ) {}

    public function notificar(string $destino, string $mensaje): void
    {
        $this->canal->enviar($destino, $mensaje);
    }
}

// En producción:
$servicio = new ServicioNotificacionesV2(NotificacionFactory::crear('email'));
// En tests:
$mock     = $this->createMock(Notificacion::class);
$servicio = new ServicioNotificacionesV2($mock);
?>

El patrón Factory es uno de los patrones del Gang of Four. La documentación de POO en PHP proporciona la base sobre interfaces, clases abstractas y herencia necesaria para implementar estos patrones con soltura.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP