Creational Patterns
Pregunta central
¿Cómo separar la creación y configuración de objetos de su uso? Creational Patterns agrupa soluciones para que decisiones sobre construcción, tipos concretos y ensamblaje no se dispersen entre consumidores que solo necesitan una capacidad.
La familia pertenece a la taxonomía clásica de Gamma, Helm, Johnson y Vlissides. El catálogo incluye Factory Method, Abstract Factory, Builder, Prototype y Singleton; no implica que toda creación necesite un patrón. Design Patterns: Elements of Reusable Object-Oriented Software.
design-pattern conserva el concepto general; pattern-selection explica cómo evaluar una presión real antes de elegir una solución. design-patterns ofrece navegación hacia las familias vecinas.
Qué decisiones puede aislar
Construir incluye elegir implementación, proporcionar configuración y colaboradores, validar combinaciones y decidir quién administra recursos. El tipo creado, su lifetime y la autoridad para usarlo son preguntas relacionadas, pero una solución creacional no las resuelve automáticamente.
Un constructor directo es una buena respuesta cuando expresa claramente una dependencia estable. Repetir new en varios lugares no demuestra por sí solo un problema; interesa si los consumidores deben cambiar juntos cada vez que varía el ensamblaje.
Usaremos ejemplos propios de un subsistema que exporta reportes. Sus consumidores necesitan producir resultados; algunos cambios afectan formato, configuración o capacidades. No se propone implementar todos los patrones en ese subsistema: cada caso permite examinar una presión distinta.
Cinco soluciones representativas
Factory Method
Factory Method en su formulación GoF usa un método de creación redefinible por subtipos. No equivale a cualquier función con nombre Create ni necesariamente a una static factory. La distinción histórica y variantes de factories se recogen en Principles of Software Construction, CMU.
En nuestro ejemplo, un workflow base produce un reporte y delega la creación del exportador a una extensión especializada. El workflow conoce la capacidad de exportar; la extensión elige la implementación. La señal es un protocolo de trabajo común cuya pieza creada varía sin alterar ese protocolo.
El riesgo es introducir herencia solamente para ocultar una llamada de construcción. Si seleccionar una función o pasar un exportador resulta más claro, no hace falta una jerarquía. «Hay varios formatos» permite explorar candidatos, no obliga a Factory Method.
Abstract Factory
Abstract Factory crea familias compatibles de productos relacionados sin exponer sus clases concretas. El extracto original GoF desarrolla esa obligación de familia. Abstract Factory.
Nuestro exportador regulatorio necesita un formatter, un validador y un productor de firmas que correspondan a la misma versión de especificación. Una factory seleccionada para esa versión entrega productos compatibles. La señal importante es compatibilidad conjunta, no solamente contar implementaciones alternativas.
El costo aparece cuando se agregan nuevos tipos de producto: pueden cambiar todas las factories. Una interfaz con decenas de métodos para productos sin relación acaba siendo un almacén de constructores. También deben probarse las combinaciones reales; pertenecer a una factory no demuestra compatibilidad semántica.
Builder
Builder separa el proceso de construcción de la representación resultante. En nuestro ejemplo, armar un paquete de exportación requiere encabezado, contenido, anexos y metadatos; diferentes builders pueden interpretar esas etapas para formatos distintos.
La señal es ensamblaje complejo con pasos o configuraciones cuya coherencia merece un punto explícito de control. Un API fluent de opciones es una variante posible, pero encadenar setters no demuestra toda la separación clásica.
El riesgo es permitir un producto incompleto o inválido porque cada opción aislada parece válida. Conviene decidir cuándo se verifica la combinación, si el builder puede reutilizarse y si conserva datos de una construcción anterior. Para tres argumentos claros, un constructor o función puede costar menos.
Prototype
Crea instancias a partir de ejemplos configurados mediante copia. Nuestro editor de reportes podría duplicar una configuración conocida y modificar el idioma o formato, sin reconstruir manualmente todas sus opciones.
La señal es una configuración inicial costosa o determinada en runtime que conviene reproducir. El riesgo es no definir qué significa copiar: compartir una colección mutable, una conexión o un identificador puede producir efectos inesperados.
Una copia profunda tampoco es automáticamente correcta. Recursos, ownership y valores que deben regenerarse necesitan reglas explícitas. Copiar configuración no debería copiar accidentalmente autoridad, sesiones activas o identidad de un documento de negocio.
Singleton
La intención original combina una instancia única con acceso global. Esas dos decisiones deben evaluarse por separado: que un recurso se comparta no justifica necesariamente ocultar quién depende de él.
En nuestro ejemplo, convertir el registro de exportadores en Singleton permite obtenerlo desde cualquier lugar. La señal legítima sería una necesidad concreta de controlar instancias dentro de una frontera definida. El misuse frecuente es usarlo como «la mejor forma de implementar cualquier servicio» y dejar el lifetime implícito.
Las C++ Core Guidelines, I.3 critican esta forma de globalidad. La entrevista retrospectiva GoF también recoge reservas de Gamma sobre Singleton. No se deduce que todo recurso compartido deba prohibirse. Entrevista a los autores, 2009.
Singleton: límites y costos
Un registro global mutable introduce dependencias ocultas. Una prueba puede modificarlo y afectar otra; resetearlo para cada test no soluciona necesariamente el diseño de su ownership. Inicialización, cierre y orden de acceso se vuelven obligaciones que los consumidores no ven.
Asegurar que la instancia se construya una sola vez no hace thread-safe sus operaciones. Dos exportaciones concurrentes pueden modificar la misma configuración. También deben decidirse recuperación de una inicialización fallida y liberación de recursos durante shutdown.
Singleton no significa una instancia en todo un sistema distribuido. Un singleton local puede existir una vez por proceso, instancia de aplicación o ámbito de carga, según runtime. Varias réplicas o contenedores pueden tener cada uno el suyo. Garantizar exclusión o autoridad entre réplicas exige mecanismos adicionales, no un campo estático.
Una alternativa en nuestro ejemplo es construir un registro durante startup y proporcionarlo explícitamente a quienes lo usan. Puede seguir habiendo una instancia compartida por aplicación, sin introducir necesariamente el patrón Singleton de acceso global.
Relación con Dependency Injection
Creational Pattern no equivale automáticamente a Dependency Injection. Suministrar colaboradores reduce responsabilidad de ensamblaje en el consumidor; una factory o builder puede participar en ese ensamblaje, pero no es una identidad entre conceptos.
Fowler distingue separación de configuración y uso, inyección y Service Locator. Un contenedor es un mecanismo posible; no hace falta incorporarlo para comprender estas decisiones. Inversion of Control Containers and the Dependency Injection pattern.
En nuestro ejemplo, pasar un exportador ya construido, pasar una función que lo crea o dejar que un workflow invoque un método redefinible expresan obligaciones diferentes. Importan frecuencia de creación, contexto necesario y quién cierra el recurso. Registrar todo como «singleton» en un contenedor tampoco prueba el Singleton GoF ni unicidad distribuida.
Tres presiones de creación
| Pattern | Problema principal |
|---|---|
| factory-method-pattern | Varía la decisión de creación mediante una operación especializada del Creator. |
| abstract-factory-pattern | Varía una familia de productos relacionados y compatibles. |
| builder-pattern | La construcción necesita un proceso complejo o progresivo. |
Candidate ≠ automatic answer. Ocultar construcción no demuestra Factory Method, tener varios productos no demuestra Abstract Factory y encadenar opciones no demuestra Builder. Simple Factory y static factory describen mecanismos relacionados, sin exigir la colaboración GoF basada en extensión del Creator.
Selección, composición y pruebas
Abstract Factory puede crear Strategies compatibles con una configuración; Builder puede recibirlas para ensamblar un producto. Esas composiciones tienen sentido si resuelven presiones distintas. No conviene crear factories para builders de factories cuando un ensamblaje directo explica todo lo necesario.
Antes de elegir, describir un cambio concreto: agregar una versión regulatoria, incorporar anexos o duplicar una configuración. Comparar qué código cambia con diseño directo y con el candidato, incluyendo debugging, contratos y lifecycle.
Las pruebas deberían demostrar productos válidos, incompatibilidades rechazadas, independencia de copias y ownership de recursos. Probar que una factory devuelve cierta clase es insuficiente si el consumidor necesita capacidades que esa combinación no conserva.
Esta familia puede colaborar con structural-patterns y behavioral-patterns, sin decidir arquitectura global. Si no existe presión significativa de construcción, rechazar el patrón y mantener creación explícita es una decisión válida.