cv.cm

Programación · Patrones de diseño

Patrón Factory Method explicado con ejemplos

5 min de lectura

Respuesta corta

Factory Method es un patrón de diseño creacional que define un método para crear un objeto pero deja que las subclases decidan qué clase concreta instanciar. La clase base escribe su lógica contra una interfaz de producto y llama al método de fábrica, como createTransport(), en lugar de usar new. Así puedes añadir nuevos tipos de producto añadiendo una subclase, sin editar el código que usa los productos.

Intención

Definir una interfaz para crear un objeto, pero dejar que las subclases decidan qué clase instanciar. El patrón Factory Method permite que una clase delegue la instanciación en sus subclases. Es uno de los cinco patrones creacionales del libro de la Banda de los Cuatro (GoF), y el único creacional con ámbito de clase, porque se apoya en la herencia.

El problema que resuelve

Imagina un módulo de notificaciones. Da formato a un mensaje, lo envía y registra el resultado. Al principio solo envía correo electrónico, así que el código llama directamente a new EmailChannel(). Luego el producto necesita SMS, y después notificaciones push. Cada canal nuevo obliga a editar la misma clase y a añadir otra rama if, aunque los pasos de formato y registro nunca cambian.

El flujo de envío es estable; solo varía el tipo de canal. El patrón Factory Method traslada la única decisión variable, qué clase crear, a un único método sobrescribible.

Estructura y participantes

ParticipanteResponsabilidadEn el ejemplo
ProductInterfaz de los objetos que crea el método de fábrica.Channel
ConcreteProductImplementa la interfaz Product.EmailChannel, SmsChannel
CreatorDeclara el método de fábrica y contiene la lógica que usa el producto.Notifier
ConcreteCreatorSobrescribe el método de fábrica para devolver un ConcreteProduct concreto.EmailNotifier, SmsNotifier

Implementación en TypeScript

interface Channel {
  send(to: string, text: string): string;
}

class EmailChannel implements Channel {
  send(to: string, text: string): string { return `email to ${to}: ${text}`; }
}

class SmsChannel implements Channel {
  send(to: string, text: string): string { return `sms to ${to}: ${text.slice(0, 20)}`; }
}

abstract class Notifier {
  // The factory method: subclasses choose the concrete product.
  protected abstract createChannel(): Channel;
  // Shared logic that works with any Channel.
  notify(to: string, text: string): void {
    const channel = this.createChannel();
    const result = channel.send(to, text.trim());
    console.log("[log]", result);
  }
}

class EmailNotifier extends Notifier {
  protected createChannel(): Channel { return new EmailChannel(); }
}

class SmsNotifier extends Notifier {
  protected createChannel(): Channel { return new SmsChannel(); }
}

const notifiers: Notifier[] = [new EmailNotifier(), new SmsNotifier()];
for (const n of notifiers) n.notify("ada", " Your order has shipped today ");
// [log] email to ada: Your order has shipped today
// [log] sms to ada: Your order has shipp

Añadir notificaciones push ahora significa escribir PushChannel y PushNotifier. El método notify, y todos los que lo llaman, no cambia.

Antes de aplicar el patrón, la decisión de creación estaba enterrada en la lógica compartida:

class Notifier {
  constructor(private kind: "email" | "sms") {}
  notify(to: string, text: string): void {
    // every new channel edits this method
    const result = this.kind === "email"
      ? `email to ${to}: ${text}`
      : `sms to ${to}: ${text.slice(0, 20)}`;
    console.log("[log]", result);
  }
}

Una alternativa más ligera: pasar una función de fábrica

El patrón Factory Method usa herencia. En TypeScript a menudo obtienes la misma flexibilidad con composición, pasando una función de creación al constructor. Así te ahorras una subclase por producto:

interface Channel { send(to: string, text: string): string; }
class Notifier {
  constructor(private createChannel: () => Channel) {}
  notify(to: string, text: string): void {
    console.log("[log]", this.createChannel().send(to, text));
  }
}
const push = new Notifier(() => ({ send: (to, text) => `push to ${to}: ${text}` }));
push.notify("ada", "Hello"); // [log] push to ada: Hello

Usa la versión con subclases cuando cada creador ya tenga su propia clase por otras razones, o cuando un framework espere que extiendas una clase base. Usa la versión con función cuando la única diferencia entre las variantes sea el objeto que crean.

Cuándo usarlo y cuándo no

Usa el patrón Factory Method cuando:

  • Una clase contenga un algoritmo estable pero no pueda saber de antemano qué objetos concretos debe crear.
  • Escribas un framework o una biblioteca y quieras que los usuarios conecten sus propias clases de producto mediante subclases.
  • Quieras mantener en un solo lugar la lógica de creación de un tipo en lugar de dispersar llamadas a new.

Evítalo cuando:

  • Solo haya un tipo de producto y ninguna señal real de un segundo. Un new directo es más claro.
  • La única razón sea hacer posibles las pruebas. Pasar la dependencia por el constructor es más simple.
  • Fueras a crear una jerarquía paralela de creadores que refleje los productos uno a uno sin ninguna otra lógica.

Usos en el mundo real

  • Colecciones de Java: Iterable.iterator() se declara una vez, y cada clase de colección (ArrayList, HashSet y otras) lo sobrescribe para devolver su propia clase de iterador. El libro GoF describe este uso del patrón Factory Method para la iteración polimórfica.
  • Iterables de JavaScript: todo iterable implementa [Symbol.iterator](), un método que devuelve un iterador nuevo adecuado para esa colección.
  • Los frameworks de interfaz de usuario suelen permitir que las subclases sobrescriban un método que crea un componente, una celda o una vista, mientras la clase base controla la disposición y el ciclo de vida.

Patrones relacionados

PatrónEn qué se diferencia
Abstract FactoryCrea toda una familia de productos relacionados mediante un objeto con varios métodos de creación. Sus métodos suelen implementarse como métodos de fábrica.
Template MethodFactory Method suele llamarse desde un método plantilla, como notify llama a createChannel más arriba.
PrototypeCrea objetos copiando una instancia en lugar de crear subclases del creador, por lo que no necesita una jerarquía paralela de creadores.
BuilderSe centra en construir un objeto complejo paso a paso, no en elegir qué clase crear.

Preguntas frecuentes

¿Cuál es la diferencia entre el patrón Factory Method y el patrón Abstract Factory?

Factory Method es un único método que las subclases sobrescriben para crear un producto. Abstract Factory es un objeto con varios métodos de creación que producen una familia de productos relacionados que deben usarse juntos.

¿Una función de fábrica simple es lo mismo que el patrón Factory Method?

No exactamente. Una función como createShape(kind) con un switch dentro suele llamarse fábrica simple. El patrón Factory Method de GoF se apoya en que una subclase sobrescriba el método de creación, pero ambos ocultan new al cliente.

¿El patrón Factory Method sigue el principio abierto/cerrado?

Sí. Añades un producto nuevo añadiendo una clase de producto y una subclase de creador nuevas, sin cambiar la lógica existente del creador.

¿El método de fábrica debe ser abstracto?

No. La clase base puede ofrecer una implementación por defecto que devuelva un producto estándar, y las subclases la sobrescriben solo cuando necesitan algo distinto.

Todos los tutoriales de programación