Patrón Factory en Laravel: Crea Objetos Complejos de Forma Elegante
18 Dec 2025
10 min read
Introducción
El patrón Factory es uno de los patrones de diseño creacionales más utilizados en el desarrollo de software. Su objetivo es encapsular la lógica de creación de objetos, permitiéndote crear instancias de clases de forma flexible sin acoplar tu código a implementaciones concretas.
En este artículo, exploraremos cómo implementar el patrón Factory en Laravel con ejemplos prácticos y reales que podrás aplicar inmediatamente en tus proyectos.
Nota importante: Este artículo trata sobre el patrón de diseño Factory, no sobre las Eloquent Factories de Laravel que se usan para testing y seeders.
¿Qué es el Patrón Factory?
El patrón Factory es un patrón de diseño creacional que proporciona una interfaz para crear objetos sin especificar la clase exacta del objeto que se creará.
En lugar de usar new directamente en tu código, delegas la creación de objetos a una clase Factory que decide qué clase instanciar según ciertos criterios.
Problema que Resuelve
Imagina que tienes código como este:
class NotificationController
{
public function send(Request $request)
{
$type = $request->input('type');
if ($type === 'email') {
$notifier = new EmailNotifier();
} elseif ($type === 'sms') {
$notifier = new SmsNotifier();
} elseif ($type === 'push') {
$notifier = new PushNotifier();
} elseif ($type === 'slack') {
$notifier = new SlackNotifier();
} else {
throw new Exception("Tipo de notificación no válido");
}
$notifier->send($request->input('message'));
}
}
Problemas con este código:
✗ Viola el principio Open/Closed: Cada nuevo tipo de notificación requiere modificar el controlador
✗ Alto acoplamiento: El controlador conoce todas las implementaciones concretas
✗ Difícil de testear: No puedes inyectar fácilmente un mock
✗ Lógica duplicada: Si necesitas crear notificadores en otros lugares, duplicas este código
Tipos de Factory
Hay dos variantes principales del patrón Factory:
1. Factory Method (Método Fábrica)
Define una interfaz para crear un objeto, pero permite que las subclases decidan qué clase instanciar.
2. Abstract Factory (Fábrica Abstracta)
Proporciona una interfaz para crear familias de objetos relacionados sin especificar sus clases concretas.
En este artículo nos enfocaremos principalmente en Factory Method con ejemplos prácticos en Laravel.
Implementando el Patrón Factory en Laravel
Vamos a refactorizar el ejemplo anterior usando el patrón Factory.
Paso 1: Crear la Interfaz
Primero, definimos un contrato común para todos los notificadores:
<?php
namespace App\Contracts;
interface NotifierInterface
{
public function send(string $recipient, string $message): bool;
}
Paso 2: Implementar las Clases Concretas
Cada tipo de notificación implementa la interfaz:
<?php
namespace App\Services\Notifications;
use App\Contracts\NotifierInterface;
class EmailNotifier implements NotifierInterface
{
public function send(string $recipient, string $message): bool
{
// Lógica para enviar email
Mail::to($recipient)->send(new NotificationMail($message));
return true;
}
}
<?php
namespace App\Services\Notifications;
use App\Contracts\NotifierInterface;
class SmsNotifier implements NotifierInterface
{
public function send(string $recipient, string $message): bool
{
// Lógica para enviar SMS (Twilio, etc.)
return Twilio::sendSms($recipient, $message);
}
}
<?php
namespace App\Services\Notifications;
use App\Contracts\NotifierInterface;
class PushNotifier implements NotifierInterface
{
public function send(string $recipient, string $message): bool
{
// Lógica para enviar notificación push
return FCM::sendNotification($recipient, $message);
}
}
Paso 3: Crear la Factory
Ahora creamos la clase Factory que encapsula la lógica de creación:
<?php
namespace App\Factories;
use App\Contracts\NotifierInterface;
use App\Services\Notifications\EmailNotifier;
use App\Services\Notifications\SmsNotifier;
use App\Services\Notifications\PushNotifier;
use App\Services\Notifications\SlackNotifier;
use InvalidArgumentException;
class NotifierFactory
{
/**
* Crear un notificador según el tipo especificado.
*/
public function make(string $type): NotifierInterface
{
return match ($type) {
'email' => new EmailNotifier(),
'sms' => new SmsNotifier(),
'push' => new PushNotifier(),
'slack' => new SlackNotifier(),
default => throw new InvalidArgumentException("Tipo de notificador no soportado: {$type}")
};
}
}
Paso 4: Usar la Factory
Ahora el controlador queda mucho más limpio:
<?php
namespace App\Http\Controllers;
use App\Factories\NotifierFactory;
use Illuminate\Http\Request;
class NotificationController extends Controller
{
public function __construct(
private NotifierFactory $notifierFactory
) {}
public function send(Request $request)
{
$validated = $request->validate([
'type' => 'required|string|in:email,sms,push,slack',
'recipient' => 'required|string',
'message' => 'required|string'
]);
$notifier = $this->notifierFactory->make($validated['type']);
$notifier->send($validated['recipient'], $validated['message']);
return response()->json(['success' => true]);
}
}
Beneficios de esta implementación:
✓ Código desacoplado: El controlador no conoce las implementaciones concretas
✓ Fácil extensión: Agregar un nuevo notificador solo requiere modificar la Factory
✓ Testeable: Puedes mockear la Factory fácilmente
✓ Reutilizable: La Factory puede usarse en cualquier parte de la aplicación
Caso Práctico: Factory para Procesadores de Pago
Veamos un ejemplo más complejo: una Factory para diferentes pasarelas de pago.
La Interfaz
<?php
namespace App\Contracts;
interface PaymentGatewayInterface
{
public function charge(float $amount, array $paymentData): PaymentResult;
public function refund(string $transactionId, float $amount): RefundResult;
}
La Factory
<?php
namespace App\Factories;
use App\Contracts\PaymentGatewayInterface;
use App\Services\Payments\StripeGateway;
use App\Services\Payments\PayPalGateway;
use App\Services\Payments\MercadoPagoGateway;
use InvalidArgumentException;
class PaymentGatewayFactory
{
/**
* Crear una pasarela de pago según el tipo especificado.
*/
public function make(string $gateway): PaymentGatewayInterface
{
return match ($gateway) {
'stripe' => new StripeGateway(),
'paypal' => new PayPalGateway(),
'mercadopago' => new MercadoPagoGateway(),
default => throw new InvalidArgumentException("Pasarela de pago no soportada: {$gateway}")
};
}
/**
* Crear la pasarela de pago por defecto.
*/
public function makeDefault(): PaymentGatewayInterface
{
return $this->make(config('payments.default', 'stripe'));
}
}
Uso en un Servicio
<?php
namespace App\Services;
use App\Factories\PaymentGatewayFactory;
class CheckoutService
{
public function __construct(
private PaymentGatewayFactory $gatewayFactory
) {}
public function processPayment(Order $order, string $gateway = null)
{
// Usar pasarela especificada o la por defecto
$paymentGateway = $gateway
? $this->gatewayFactory->make($gateway)
: $this->gatewayFactory->makeDefault();
$result = $paymentGateway->charge(
amount: $order->total,
paymentData: $order->paymentData
);
if ($result->isSuccess()) {
$order->markAsPaid($result->transactionId);
}
return $result;
}
}
Factory con Parámetros Complejos
A veces necesitas pasar parámetros específicos al crear objetos. Veamos cómo manejarlo:
<?php
namespace App\Factories;
use App\Services\Reports\PdfReportGenerator;
use App\Services\Reports\ExcelReportGenerator;
use App\Services\Reports\CsvReportGenerator;
use App\Contracts\ReportGeneratorInterface;
class ReportFactory
{
public function make(string $format, array $options = []): ReportGeneratorInterface
{
return match ($format) {
'pdf' => new PdfReportGenerator(
orientation: $options['orientation'] ?? 'portrait',
pageSize: $options['pageSize'] ?? 'A4',
includeHeaders: $options['includeHeaders'] ?? true
),
'excel' => new ExcelReportGenerator(
sheetName: $options['sheetName'] ?? 'Report',
includeFormulas: $options['includeFormulas'] ?? false
),
'csv' => new CsvReportGenerator(
delimiter: $options['delimiter'] ?? ',',
enclosure: $options['enclosure'] ?? '"'
),
default => throw new InvalidArgumentException("Formato no soportado: {$format}")
};
}
}
Uso:
$reportFactory = app(ReportFactory::class);
// Crear PDF con opciones personalizadas
$pdfGenerator = $reportFactory->make('pdf', [
'orientation' => 'landscape',
'pageSize' => 'A3',
'includeHeaders' => true
]);
$report = $pdfGenerator->generate($data);
Testing con Factories
El patrón Factory hace que el testing sea mucho más sencillo:
<?php
namespace Tests\Unit;
use App\Factories\NotifierFactory;
use App\Contracts\NotifierInterface;
use Tests\TestCase;
use Mockery;
class NotificationServiceTest extends TestCase
{
public function test_sends_email_notification()
{
// Crear un mock del notificador
$mockNotifier = Mockery::mock(NotifierInterface::class);
$mockNotifier->shouldReceive('send')
->once()
->with('[email protected]', 'Test message')
->andReturn(true);
// Crear un mock de la factory
$mockFactory = Mockery::mock(NotifierFactory::class);
$mockFactory->shouldReceive('make')
->with('email')
->once()
->andReturn($mockNotifier);
// Inyectar el mock en el servicio
$service = new NotificationService($mockFactory);
$result = $service->notify('email', '[email protected]', 'Test message');
$this->assertTrue($result);
}
}
Cuándo Usar el Patrón Factory
✓ Usa Factory cuando:
- Tienes múltiples implementaciones de una interfaz que se seleccionan en runtime
- La creación de objetos es compleja y necesita encapsularse
- Quieres desacoplar tu código de implementaciones concretas
- Necesitas flexibilidad para agregar nuevas implementaciones sin modificar código existente
- Quieres facilitar el testing con mocks
✗ No uses Factory cuando:
- Solo tienes una implementación y no esperas más
- La creación del objeto es trivial (un simple
newes suficiente) - Estás agregando complejidad innecesaria
- El objeto no tiene variaciones ni configuraciones complejas
Ventajas del Patrón Factory
✓ Desacoplamiento: El código cliente no depende de clases concretas
✓ Extensibilidad: Agregar nuevas implementaciones es fácil
✓ Single Responsibility: La lógica de creación está en un solo lugar
✓ Open/Closed: Cumple con el principio Open/Closed de SOLID
✓ Testeable: Facilita el uso de mocks y stubs
✓ Mantenibilidad: Los cambios están localizados en la Factory
Desventajas y Consideraciones
✗ Complejidad adicional: Introduces más clases al sistema
✗ Overhead: Para casos simples puede ser overkill
⚠️ Cuidado con: God Factories que hacen demasiadas cosas
Mejores Prácticas
1. Usa Type Hints
public function make(string $type): NotifierInterface
{
// El tipo de retorno garantiza que siempre devuelves la interfaz correcta
}
2. Valida el Input
public function make(string $type): NotifierInterface
{
if (!isset($this->notifiers[$type])) {
throw new InvalidArgumentException("Tipo no soportado: {$type}");
}
return $this->container->make($this->notifiers[$type]);
}
3. Usa Configuración para Flexibilidad
public function __construct(Container $container)
{
$this->notifiers = config('notifications.channels', []);
}
4. Proporciona Métodos Helper
public function makeDefault(): NotifierInterface
{
return $this->make(config('notifications.default'));
}
public function getAvailableTypes(): array
{
return array_keys($this->notifiers);
}
5. Documenta Tu Factory
/**
* Factory para crear instancias de notificadores.
*
* Esta factory soporta los siguientes tipos:
* - email: Envío vía correo electrónico
* - sms: Envío vía SMS usando Twilio
* - push: Notificaciones push con FCM
* - slack: Mensajes a canales de Slack
*
* @example
* $notifier = $factory->make('email');
* $notifier->send('user@example.com', 'Hello!');
*/
class NotifierFactory
{
// ...
}
Conclusión
El patrón Factory es una herramienta esencial en tu arsenal de patrones de diseño. Te permite crear objetos de forma flexible y mantenible, siguiendo los principios SOLID y facilitando el testing.
En Laravel, el patrón Factory se integra perfectamente con el contenedor de inyección de dependencias, permitiéndote crear Factories potentes y fáciles de usar.
Recapitulación:
✓ El patrón Factory encapsula la lógica de creación de objetos
✓ Desacopla tu código de implementaciones concretas
✓ Facilita la extensión y el mantenimiento
✓ Se integra perfectamente con el contenedor de Laravel
✓ Mejora significativamente la testabilidad
No se trata de aplicar el patrón Factory en todos lados, sino de usarlo cuando realmente aporta valor. Cuando tengas múltiples implementaciones de una interfaz y necesites flexibilidad para elegir cuál usar, el patrón Factory es tu mejor aliado.
¿Quieres más?
Si te gustó este artículo y quieres llevar tus Factories al siguiente nivel, no te pierdas la segunda parte:
Patrón Factory Avanzado en Laravel: Container, Service Locator y Abstract Factory
Aprenderás técnicas avanzadas como:
- Usar el Container de Laravel para inyección automática
- Implementar Abstract Factory para familias de objetos
- Factory con caché para optimización
- Evitar anti-patrones como Service Locator
- Y mucho más…
Artículos Relacionados
Si te gustó este artículo, te recomiendo leer:
- Guía Completa de Principios SOLID en Laravel - Aprende los 5 principios que todo desarrollador debe conocer
- Principio Open/Closed en Laravel - Cómo extender funcionalidad sin modificar código existente
- Principio de Inversión de Dependencias - Desacopla tu código con abstracciones
Happy coding! 🚀
C O M E N T A R I O S
Deja un comentario
Tu email no será publicado. Los campos marcados con * son obligatorios.
Cargando comentarios...
☕ ¿Te ha sido útil este artículo?
Apóyame con un café mientras sigo creando contenido técnico
☕ Invítame un café