Design Patterns - Factory Method

Propósito

El Factory Method define una interfaz para crear un objeto, pero deja que las subclases decidan qué clase instanciar. Delega en las subclases la creación del objeto.

Problema

La necesidad de crear objetos sin especificar la clase del objeto que se va a crear. En un ejemplo del ámbito de la cocina: debemos preparar un plato, pero no sabemos si será una sopa, una ensalada o un postre hasta el último momento.

Solución

La solución que ofrece el Factory Method es:

  • Definir una interfaz de creación de objetos: Crear una interfaz o método abstracto para la creación de objetos
  • Delegar la creación a las subclases: Las subclases tendrán la implementación concreta para la creación de objetos.

Como resultado, el cliente puede trabajar con interfaces o clases abstractas sin tener que conocer las clases concretas.

Es como tener un método general para “preparar un plato”, pero las subrecetas (sopas, ensaladas, postres) son las que determinan los ingredientes y procedimientos específicos.

Estructura

factory method

Participantes

  • Creador: Define el método abstracto para la creación del objeto.
  • CreadorConcreto: Implementa el método factoryMethod. Devuelve una instancia del ProductoConcreto.
  • Producto: Interfaz o clase abstracta de los objetos que se crearán.
  • ProductoConcreto: Implementación específica del producto que se crea.

Cuándo Usarlo

Este patrón es recomendable cuando:

  • Una clase no puede prever la clase de objetos que debe crear.
  • Una clase quiere que sean las subclases quienes decidan los objetos que crea

Ventajas

Flexibilidad: al delegar la creación de objetos a las subclases.

Desacoplamiento: separa el código de construcción del código propio del producto

Desventajas

Complejidad: requiere la creación de múltiples subclases, y puede llevar a mayor tiempo de desarrollo

Ejemplo: Sistema de Gestión de Documentos

Pensemos en un sistema de gestión de documentos.

En este sistema los usuarios pueden crear diferentes tipos de documentos… como documentos de texto, planilas de cálculo (spreadsheets), presentaciones, etc.

Cada tipo de documento tiene características y comportamientos propios, pero comparten funcionalidades básicas… como abrir, cerrar y guardar.

Problema

Los usuarios deben poder crear un documento, pero el tipo exacto de documento dependerá de la elección del usuario.

Por lo tanto, necesitamos una manera de crear tipos de documentos sin especificar las clases concretas de los documentos, hasta el momento de ejecución.

Solución planteada

Implementamos un Factory Method que permite a las subclases decidir qué clase de documento instanciar.

Por un lado tenemos los documentos, bajo la clase abstracta o interfaz Document. Una clase que hereda o implementa Document para cada tipo (Text, Spreadsheet), cada una implementa su propio comportamiento para cada acción.

Para la creación de objetos, definimos la clase abstracta DocumentCreator con el método abstracto createDocument().

Para cada tipo de documento (Text, Spreedsheet) creamos una subclase que implementa createDocument(). Cada una va a devolver una instancia del documento correspondiente.

DocumentManager actuaría como el cliente, que crea el documento que quiere.

factory method

Código Java

Codificamos en Java lo que preparamos en el diagrama.

Definimos la clase abstracta Document con las operaciones comunes a todos los documentos, abrir, cerrar y guardar:


    abstract class Document {
      public abstract void open();
      public abstract void close();
      public abstract void save();
    }
                

Implementamos subclases concretas para cada documento, cada una con su propio comportamiento:


    class TextDocument extends Document {
      @Override
      public void open() { 
          /* Implementación específica para abrir documentos de texto */ 
      }
      @Override
      public void close() { 
          /* Implementación específica para cerrar documentos de texto */ 
      }
      @Override
      public void save() { 
          /* Implementación específica para guardar documentos de texto */ 
      }
    }
                

    class SpreadsheetDocument extends Document {
      @Override
      public void open() { 
          /* Implementación específica para abrir hojas de cálculo */ 
      }
      @Override
      public void close() { 
          /* Implementación específica para cerrar hojas de cálculo */ 
      }
      @Override
      public void save() { 
          /* Implementación específica para guardar hojas de cálculo */ 
      }
    }
                

Definimos la clase abstracta DocumentCreator con el método createDocument():


    abstract class DocumentCreator {
      public abstract Document createDocument();
    }
                

Implementamos DocumentCreator para cada tipo de documento, cada una devuelve una instancia del documento que corresponde:


    public class TextDocumentCreator  extends DocumentCreator {
      @Override
      public Document createDocument() {
          return new TextDocument();
      }
    }
                

    public class SpreadsheetDocumentCreator  extends DocumentCreator {
      @Override
      public Document createDocument() {
          return new SpreadsheetDocument();
      }
    }
                

Finalmente, en el código cliente, creamos instancias mediante estos creators:


    public class DocumentManager {
      public static void main(String[] args) { 
          DocumentCreator creator =   new TextDocumentCreator();
          // Cambiar a SpreadsheetDocumentCreator para crear una hoja de cálculo
          Document doc = creator.createDocument();
          doc.open();
          doc.save();
          doc.close();
      }
    }
                

Mapeo (del ejemplo a Participantes)

Los participantes que vimos antes son: Creador, CreadorConcreto, Producto, ProductoConcreto. Cliente:

  • DocumentCreator (Creador): Define el método fábrica createDocument().
  • TextDocumentCreator, SpreadsheetDocumentCreator, … (CreadorConcreto): Implementan createDocument(), retornando cada una una instancia de un tipo de documento.
  • Document (Producto): Define la interfaz común para los objetos que serán creados.
  • TextDocument, SpreadsheetDocument, … (ProductoConcreto): Implementaciones específicas de cada tipo de documento.
  • DocumentManager (Cliente): Decide qué documento crear

Conclusiones

En este ejemplo, DocumentManager (el cliente) no necesita saber qué tipo de documento está creando, simplemente utiliza el DocumentCreator correspondiente.

Este enfoque mantiene el código flexible y extensible, permitiendo agregar fácilmente nuevos tipos de documentos sin modificar el código existente.

Patrones relacionados

  • Abstract Factory

Factory Method crea un solo objeto mientras que Abstract Factory crea una  familia de objetos relacionados o dependientes.

  • Builder

Builder se centra en construir objetos complejos paso a paso, a diferencia del Factory Method, que crea un objeto en un solo paso. Builder también da control sobre el proceso de construcción.

  • Prototype

Prototype se basa en la clonación de objetos existentes, No necesita heredar de un Creador, pero suele requerir una operación inicializar en la clase Producto.

  • Factory

Existe un patrón conocido como Factory (o Simple Factory) que no es el Factory Method y muchas veces se lo confunde. Factory es un concepto más simple, no requiere de herencia, solo se centra en encapsular la creación de objetos.  No es un patrón oficial de Gang of Four, pero es algo muy usado en el ambiente por su simplicidad.

  • Template Method

Los métodos de fabricación muchas veces son invocados desde el interior de un Template Method.