Diseño de software

Fábricas estáticas en Swift: por qué usar make en lugar de init

Los métodos de fábrica estáticos de Swift expresan la intención de creación mediante nombres y controlan el tipo devuelto, la caché y la reutilización de instancias. Este resumen cubre las ventajas y desventajas de elegir static func make frente a init y sus diferencias con el patrón GoF.

4 min de lectura
Imagen de portada de Fábricas estáticas en Swift: por qué usar make en lugar de init

Al desarrollar para iOS, quizá hayas encontrado init(...) en lugar de static func make(...) en código ajeno y te hayas preguntado por qué.

Empecemos por la conclusión.

Los métodos de fábrica estáticos de Swift nombran el proceso de creación, permiten tipos de retorno flexibles y controlan la reutilización de objetos: una opción cuando init resulta limitado.

Hoy explicaré desde la práctica por qué usar static func make en lugar de init.

La fábrica estática de este artículo solo se parece por el nombre al Factory Method Pattern de GoF, donde las subclases deciden la creación. Hay más ejemplos de Swift en Cómo separar la lógica de creación con funciones de fábrica y los límites del patrón GoF se tratan en Factory Method frente a Abstract Factory.


¿Qué es exactamente un método de fábrica estático?

El nombre suena grandioso, pero el concepto es sencillo.

Es un método static que crea y devuelve un objeto. En vez de llamar directamente al constructor, añades una función que envuelve la creación.

Se ve así.

struct Button {
    let title: String
    let style: Style

    // init envuelto en un método de fábrica estático
    static func makePrimary(title: String) -> Button {
        Button(title: title, style: .primary)
    }
}

La llamada queda así: Button.makePrimary(title: "확인").

El resultado es igual que llamar directamente a Button(title:style:), pero el nombre deja claro «qué botón se crea».

Esta pequeña diferencia importa bastante en producción.


4 razones para usar make en lugar de init

Estas son las ventajas que he notado en proyectos reales.

1. Expresar la intención con el nombre

Todos los constructores tienen el mismo nombre: init. Solo se distinguen por las combinaciones de parámetros.

Por eso, varios init parecidos confunden. Nombres como make(fromJSON:) y make(withDefaults:) dejan claro qué se crea con solo mirar la llamada.

2. No hace falta crear un objeto nuevo cada vez

init crea siempre una instancia nueva al llamarse.

En cambio, una fábrica puede devolver un objeto en caché o un singleton existente. Para valores fijos, como los valores verdadero y falso de Bool, reutilizar es mucho más eficiente.

3. El tipo de retorno puede ser flexible

Personalmente, me parece la ventaja más potente.

Una fábrica puede devolver un subtipo del tipo declarado o una implementación de protocolo. El código cliente no necesita conocer el tipo concreto.

protocol Shape { func area() -> Double }

enum ShapeFactory {
    // devolver distintas implementaciones según la condición
    static func make(sides: Int) -> Shape {
        sides == 4 ? Square() : Triangle()
    }
}

make(sides:) solo tiene Shape como tipo de retorno, pero en ejecución aparece la implementación adecuada.

Parece un único Shape, pero contiene un tipo distinto según la situación
Parece un único Shape, pero contiene un tipo distinto según la situación

4. Los fallos se pueden gestionar con suavidad

Podrías usar init? o throws, pero una fábrica facilita devolver un opcional o envolver el resultado en Result. Cuando la creación es compleja, el flujo queda más limpio.

Con una sola línea de make, la llamada se entiende mucho mejor
Con una sola línea de make, la llamada se entiende mucho mejor

¿Qué cambia respecto a init? Comparación rápida

Para evitar confusiones, lo resumo en una tabla.

Categoría init (constructor) static func make (fábrica estática)
Nombre Siempre init Se puede elegir libremente
Instancia nueva Creación obligatoria cada vez Reutilización y caché posibles
Tipo de retorno Solo su propio tipo Subtipo o protocolo posible
Gestión de fallos init? / throws Opcional, Result, etc., con flexibilidad
Desventaja No distingue nombres Limitaciones de subclasificación; menor descubribilidad

También conviene hablar de las desventajas.

Si solo existe el método de fábrica y no hay un init público, es difícil ampliar el tipo mediante herencia.

Los constructores aparecen en el autocompletado, pero make es más fácil de encontrar si conoces su nombre, así que tiene menor descubribilidad. Recomiendo nombres convencionales como make, create y from.


Entonces, ¿cuándo conviene usarlo?

Así lo decido en la práctica.

  1. Solo hay que rellenar propiedades almacenadas → usa init
  2. Hay varias formas de creación y quieres distinguirlas por nombre → make
  3. Debes devolver tipos distintos según la condición → make
  4. Quieres reutilizar o guardar objetos en caché → make
Uso esta tabla para decidir entre ambos
Uso esta tabla para decidir entre ambos

En resumen, las fábricas brillan cuando crear deja de ser simplemente «rellenar valores» y se convierte en una decisión.

Pero envolver toda creación en make vuelve el código verboso. La clave es usarlo solo cuando haga falta.

Al principio puede resultar extraño, pero cuando entiendes la intención, leer código cambia. La próxima vez que veas un caso parecido, prueba make; seguramente te ayudará 🙂

Lecturas recomendadas