🚨 ¡Nueva review! ✨ Mi ratón favorito para programar: el Logitech MX Master 3S . ¡Échale un ojo! 👀

Patrón Factory Method: la fábrica de objetos más elegante

Delega la creación de objetos a fábricas especializadas y olvídate del new directo

Escrito por domin el 14 de septiembre de 2025 · Actualizado el 8 de febrero de 2026

🏭 Patrón Factory Method: la fábrica de objetos más elegante

¿Qué es?

El Factory Method es un patrón de diseño creacional del catálogo Gang of Four (GoF). Esto quiere decir que nos va a ayudar a crear objetos sin tener que escribir directamente la lógica de creación en varios lugares del código.

Gato negro representando el patrón de diseño Factory Method.

Imagínate que quieres fabricar coches Cupra desde varias partes del código. Esto se puede volver un auténtico coñazo de mantener, pero con un Factory Method se soluciona todo.

Pero cuidadito shavá, no es solo meter todo en una clase y ya está. El Factory Method es más elegante: defines una “fábrica abstracta” que dice “oye, aquí se van a crear coches, pero no sé cuáles exactamente”. Luego tienes fábricas específicas que heredan de esta y cada una sabe crear su rollo.

Por ejemplo, tienes una CupraFactory abstracta que dice “aquí se crean coches Cupra” pero no especifica cuáles. Luego tienes CupraLeonFactory que hereda de la anterior y, cuando le pides un coche, te fabrica un León flamante. También tienes CupraFormentorFactory que hace lo mismo, pero con Formentors.

Lo guapo de esto es que si mañana Cupra saca un modelo nuevo, solo creas una nueva fábrica (CupraAtecaFactory por ejemplo) sin tocar ni una línea del código que ya tienes. Tu código que pide coches no tiene ni idea de si está pidiendo un León o un Formentor, solo sabe que está pidiendo a una fábrica Cupra y le van a dar un coche Cupra.

Pues esto es el Factory Method: cada fábrica sabe crear su producto específico, pero todas comparten la misma interfaz para pedírselo.

Este POST no lo patrocina Cupra.


La estructura del patrón

Product (Interfaz)

Define la interfaz que comparten todos los productos que las fábricas van a crear. En nuestro caso: CupraCoche.

Concrete Products

Las implementaciones concretas del producto. Cada una con su lógica: CupraLeon, CupraFormentor, CupraAteca.

Creator (Fábrica abstracta)

Declara el método crearCoche() que retorna un Product. No sabe qué producto concreto crear, eso lo deciden las hijas.

Concrete Creators

Cada fábrica concreta sobreescribe el factory method y crea su producto específico: CupraLeonFactory, CupraFormentorFactory...

La clave está en que el código cliente trabaja con la fábrica abstracta y la interfaz del producto. No sabe ni le importa qué producto concreto se está creando. Eso lo decide la fábrica concreta.


¿Qué problema resuelve?

Imagínate que Cupra va sacando varios modelos de coche y nosotros tenemos un switch gigante repartido por el código con la lógica de creación mezclada:

// ❌ El switch infernal que crece sin parar
$modelo = $_GET['modelo'] ?? 'leon';

switch ($modelo) {
    case 'leon':
        $coche = new CupraLeon();
        break;
    case 'formentor':
        $coche = new CupraFormentor();
        break;
    case 'ateca':
        $coche = new CupraAteca();
        break;
    default:
        throw new Exception("Modelo desconocido");
}

$coche->arrancar();

¿Qué problemas tiene esto?


Implementación con Factory Method

Primero, los productos (los coches):

<?php

interface CupraCoche
{
    public function arrancar(): string;
    public function getModelo(): string;
    public function getPotencia(): int;
}

class CupraLeon implements CupraCoche
{
    public function arrancar(): string
    {
        return "León: ¡Brrrum! 300 CV";
    }

    public function getModelo(): string
    {
        return "Cupra León";
    }

    public function getPotencia(): int
    {
        return 300;
    }
}

class CupraFormentor implements CupraCoche
{
    public function arrancar(): string
    {
        return "Formentor: ¡Zas! 310 CV";
    }

    public function getModelo(): string
    {
        return "Cupra Formentor";
    }

    public function getPotencia(): int
    {
        return 310;
    }
}

class CupraAteca implements CupraCoche
{
    public function arrancar(): string
    {
        return "Ateca: ¡Prrr! 300 CV";
    }

    public function getModelo(): string
    {
        return "Cupra Ateca";
    }

    public function getPotencia(): int
    {
        return 300;
    }
}

Ahora la fábrica abstracta y sus fábricas concretas:

<?php

abstract class CupraFactory
{
    // El Factory Method: las subclases deciden qué crear
    abstract public function crearCoche(): CupraCoche;

    // Lógica compartida que usa el factory method
    public function entregarCoche(): string
    {
        $coche = $this->crearCoche();
        return "Entregando: " . $coche->getModelo()
             . " (" . $coche->getPotencia() . " CV)";
    }
}

class CupraLeonFactory extends CupraFactory
{
    public function crearCoche(): CupraCoche
    {
        return new CupraLeon();
    }
}

class CupraFormentorFactory extends CupraFactory
{
    public function crearCoche(): CupraCoche
    {
        return new CupraFormentor();
    }
}

class CupraAtecaFactory extends CupraFactory
{
    public function crearCoche(): CupraCoche
    {
        return new CupraAteca();
    }
}

Ahora nos podemos cargar el switch y dejarlo más limpio. ¡Mira qué guapo pavo!

// index.php
$modelo = $_GET['modelo'] ?? 'leon';

// Fábrica "mapeada" (array asociativo)
$fabricas = [
    'leon'      => new CupraLeonFactory(),
    'formentor' => new CupraFormentorFactory(),
    'ateca'     => new CupraAtecaFactory(),
];

if (!isset($fabricas[$modelo])) {
    throw new Exception("Modelo desconocido");
}

$coche = $fabricas[$modelo]->crearCoche(); // ¡Una sola línea!
echo $coche->arrancar();

// O usando la lógica compartida del Creator:
echo $fabricas[$modelo]->entregarCoche();
// "Entregando: Cupra León (300 CV)"

Si aparece un nuevo modelo de coche, es fácil: creas la clase del coche, creas su fábrica, la añades al array y listo. Cero líneas modificadas en el código existente. OCP cumplido.


Ejemplo del mundo real: sistema de notificaciones

Vamos a ver un ejemplo que seguro te encuentras en proyectos reales. Un sistema que envía notificaciones por diferentes canales:

<?php

// Product: la interfaz de notificación
interface Notification
{
    public function send(string $recipient, string $message): bool;
    public function getChannel(): string;
}

// Concrete Products
class EmailNotification implements Notification
{
    public function send(string $recipient, string $message): bool
    {
        // Configurar SMTP, construir email, enviar...
        echo "Enviando email a $recipient: $message\n";
        return true;
    }

    public function getChannel(): string
    {
        return "email";
    }
}

class SmsNotification implements Notification
{
    public function send(string $recipient, string $message): bool
    {
        // Conectar con API de SMS, enviar...
        echo "Enviando SMS a $recipient: $message\n";
        return true;
    }

    public function getChannel(): string
    {
        return "sms";
    }
}

class PushNotification implements Notification
{
    public function send(string $recipient, string $message): bool
    {
        // Conectar con servicio de push...
        echo "Enviando push a $recipient: $message\n";
        return true;
    }

    public function getChannel(): string
    {
        return "push";
    }
}

// Creator: fábrica abstracta
abstract class NotificationFactory
{
    abstract public function createNotification(): Notification;

    public function notify(string $recipient, string $message): bool
    {
        $notification = $this->createNotification();
        echo "[" . $notification->getChannel() . "] ";
        return $notification->send($recipient, $message);
    }
}

// Concrete Creators
class EmailNotificationFactory extends NotificationFactory
{
    public function createNotification(): Notification
    {
        return new EmailNotification();
    }
}

class SmsNotificationFactory extends NotificationFactory
{
    public function createNotification(): Notification
    {
        return new SmsNotification();
    }
}

class PushNotificationFactory extends NotificationFactory
{
    public function createNotification(): Notification
    {
        return new PushNotification();
    }
}

El código cliente trabaja con la fábrica abstracta:

function alertarUsuario(NotificationFactory $factory, string $user): void
{
    // Esta función no sabe ni le importa si es email, SMS o push
    $factory->notify($user, "Tienes una alerta importante");
}

// Usar email
alertarUsuario(new EmailNotificationFactory(), "juan@email.com");
// [email] Enviando email a juan@email.com: Tienes una alerta importante

// Usar SMS
alertarUsuario(new SmsNotificationFactory(), "+34612345678");
// [sms] Enviando SMS a +34612345678: Tienes una alerta importante

// Mañana añades Telegram sin tocar nada de lo anterior
alertarUsuario(new TelegramNotificationFactory(), "@juancho");

La función alertarUsuario recibe una fábrica abstracta y trabaja con ella sin saber qué canal concreto hay debajo. Si mañana necesitas Telegram, Slack o paloma mensajera, solo creas la clase y su fábrica.


Otro ejemplo: generación de documentos

Imagina que tu aplicación necesita generar facturas en diferentes formatos:

<?php

interface Document
{
    public function generate(array $data): string;
    public function getExtension(): string;
}

class PdfDocument implements Document
{
    public function generate(array $data): string
    {
        // Lógica de generación de PDF...
        return "Factura generada en PDF";
    }

    public function getExtension(): string
    {
        return "pdf";
    }
}

class CsvDocument implements Document
{
    public function generate(array $data): string
    {
        // Lógica de generación de CSV...
        return implode(",", $data);
    }

    public function getExtension(): string
    {
        return "csv";
    }
}

class HtmlDocument implements Document
{
    public function generate(array $data): string
    {
        return "<table><tr><td>" . implode("</td><td>", $data) . "</td></tr></table>";
    }

    public function getExtension(): string
    {
        return "html";
    }
}

// Creator
abstract class DocumentFactory
{
    abstract public function createDocument(): Document;

    public function exportInvoice(array $invoiceData): string
    {
        $doc = $this->createDocument();
        $content = $doc->generate($invoiceData);
        $filename = "factura_" . date('Ymd') . "." . $doc->getExtension();
        // Guardar en disco, enviar por email, etc.
        return "Exportado: $filename";
    }
}

class PdfDocumentFactory extends DocumentFactory
{
    public function createDocument(): Document
    {
        return new PdfDocument();
    }
}

class CsvDocumentFactory extends DocumentFactory
{
    public function createDocument(): Document
    {
        return new CsvDocument();
    }
}

class HtmlDocumentFactory extends DocumentFactory
{
    public function createDocument(): Document
    {
        return new HtmlDocument();
    }
}

Fíjate en que exportInvoice() está en la fábrica abstracta y usa el factory method. Toda la lógica compartida (nombre del archivo, guardado en disco…) vive en el Creator. Los Concrete Creators solo se preocupan de crear el tipo de documento correcto. Eso es separación de responsabilidades.


Factory Method vs Simple Factory vs Abstract Factory

Es fácil confundir estos tres. Aquí van las diferencias:

PatrónQué haceCuándo usarlo
Simple FactoryUn solo método estático con un switch que crea el objeto según un parámetroCasos simples, pocos productos, no necesitas extender
Factory MethodDelega la creación a subclases. Cada subclase (fábrica concreta) crea un producto concretoCuando necesitas extensibilidad y cumplir OCP
Abstract FactoryCrea familias de productos relacionados. No un solo producto, sino un conjuntoCuando necesitas crear objetos que van juntos (ej: UI dark theme + light theme)

El Simple Factory no es un patrón GoF, es más bien un “idiom” de programación. El Factory Method y el Abstract Factory sí son patrones GoF oficiales.


Relación con los principios SOLID

El Factory Method es un patrón que nace naturalmente cuando aplicas SOLID:

OCP

Añadir un producto nuevo = crear una clase y una fábrica nueva. Sin tocar el código existente. Es el principio que más directamente cumple.

SRP

Cada fábrica tiene una sola responsabilidad: crear su producto. La lógica de creación está separada de la lógica de uso.

DIP

El código cliente depende de abstracciones (CupraFactory, CupraCoche), no de clases concretas.

LSP

Cualquier fábrica concreta puede sustituir a la abstracta sin romper nada. Todas cumplen el mismo contrato.


Ventajas y desventajas

Ventajas
  • OCP: Nuevos productos sin tocar código existente.
  • Desacoplamiento: El cliente no conoce las clases concretas.
  • SRP: La lógica de creación está encapsulada en cada fábrica.
  • Testeable: Puedes inyectar fábricas mock en los tests.
  • Lógica compartida: El Creator puede tener métodos comunes que usan el factory method.
Desventajas
  • Más clases: Una fábrica por cada producto puede generar muchas clases.
  • Complejidad: Para problemas simples es matar moscas a cañonazos.
  • Jerarquía paralela: La jerarquía de fábricas crece en paralelo con la de productos.

¿Cuándo usarlo y cuándo no?

Úsalo cuando:

NO lo uses cuando:

Recuerda: El Factory Method es una herramienta para cuando la complejidad lo justifica. Si un new directo funciona y el código es claro, no metas un Factory Method solo porque queda bonito.


Conclusión

El Factory Method es uno de los patrones más útiles y más usados del catálogo GoF. Su idea es sencilla pero potente: delega la creación de objetos a subclases especializadas para que el código cliente trabaje con abstracciones y no se preocupe por los detalles.

Elimina los switches gigantes de creación, cumple el OCP de SOLID y hace que añadir productos nuevos sea tan fácil como crear una clase y su fábrica. Y lo más importante: el código que usa los productos no cambia nunca, independientemente de cuántos tipos de productos tengas.

¡Ea! Nos vemos en los bares. 🍻


Pon a prueba lo aprendido

1. ¿Qué tipo de patrón de diseño es el Factory Method?

2. ¿Qué problema principal resuelve el Factory Method?

3. En el ejemplo de Cupra, ¿quién decide qué coche concreto se crea?

4. ¿Qué principio SOLID cumple más directamente el Factory Method?

5. ¿Cuál es la diferencia entre Factory Method y Simple Factory?

6. ¿Qué pasa cuando Cupra saca un modelo nuevo usando Factory Method?

7. ¿Cuál es una desventaja del Factory Method?

8. ¿Cuándo NO deberías usar Factory Method?