Diseño de software

Patrón Monostate en Swift: ¿puede ser una alternativa a Singleton?

Para quienes se han preguntado si de verdad está bien usar Singleton así

4 min de lectura
Imagen de portada de Patrón Monostate en Swift: ¿puede ser una alternativa a Singleton?

Para quienes se han preguntado si de verdad está bien usar Singleton así

Si desarrollas para iOS, probablemente no haya nadie que no haya usado Singleton. A mí también me pasó. SomeManager.shared Este tipo de código aparecía varias veces en prácticamente todos los proyectos.

Pero en algún momento, este .shared empezó a resultar incómodo. El estado se mezclaba durante las pruebas y los miembros del equipo accedían a él libremente desde cualquier parte.

Así fue como descubrí el patrón Monostate. Hoy quiero compartir mi experiencia usando Monostate en Swift y contar si realmente puede ser una alternativa a Singleton.

La conclusión, por adelantado: Monostate es un patrón que permite compartir un único estado y crear varias instancias. Tiene el mismo objetivo que Singleton, pero el enfoque es exactamente el contrario. No es una solución universal; depende de la situación. Lo veremos paso a paso.

¿Qué es exactamente el patrón Monostate?

La idea central de Singleton es crear una sola instancia. Se bloquea el inicializador y se utiliza únicamente shared.

Monostate es lo contrario. Se pueden crear tantas instancias como se quiera. En cambio, todo su estado (datos) se comparte mediante propiedades static.

Es decir, tanto si creas el objeto A como el B, ambos observan los mismos datos. Por fuera parecen objetos normales, pero por dentro están conectados al mismo estado.

En Swift, se ve aproximadamente así.

struct Settings {
    private static var _volume = 50   // compartir el estado static
    var volume: Int {                 // usar como una instancia
        get { Settings._volume }
        set { Settings._volume = newValue }
    }
}
// instancias diferentes, pero un solo estado
var a = Settings(); a.volume = 80
print(Settings().volume)  // 80

Aunque crees un nuevo Settings(), volume seguirá siendo 80. Hay varias instancias, pero un solo estado: eso es Monostate en esencia.

Hay tres instancias, pero todas observan un único estado
Hay tres instancias, pero todas observan un único estado

¿En qué se diferencia de Singleton?

La mayor diferencia está en el código que lo utiliza.

Con Singleton, el código cliente siempre debe tener presente .shared: “Esto es un Singleton”. Con Monostate, basta con crear Settings() y usarlo como un objeto normal, sin conocer la implementación interna.

La comparación queda así.

Categoría Singleton Monostate
Número de instancias Exactamente 1 Se pueden crear varias
Qué se comparte La propia instancia El estado (datos static)
Código cliente Especifica .shared explícitamente Como un objeto normal
Herencia Complicada Relativamente flexible

La herencia resulta especialmente interesante. Heredar de Singleton es bastante incómodo, mientras que Monostate es un tipo normal y, por tanto, más flexible de ampliar.


Entonces, ¿puede ser una alternativa a Singleton?

Si soy sincero, a veces sí y a veces no.

Empecemos por las ventajas. El código cliente queda más limpio y .shared no aparece repartido por todo el código como punto de acceso global. Como la interfaz es igual que la de un objeto normal, cambiar la estructura más adelante también resulta menos costoso.

Monostate es un patrón que “oculta el estado global”. Parece cómodo, pero el estado global oculto es un arma de doble filo.

El problema está aquí. Como parece un objeto normal, un miembro del equipo puede crearlo pensando: “Será solo un objeto local”, para descubrir después que su estado se comparte globalmente. Eso puede resultar aún más confuso.

Además, al tratarse de estado compartido mediante static, hay que preocuparse igualmente por los problemas de concurrencia en entornos multihilo. En esto no se diferencia de Singleton.

Por mi experiencia, en el Swift actual recomiendo considerar primero la inyección de dependencias antes que Monostate. Las pruebas son más sencillas y las dependencias quedan claras.

En proyectos Swift actuales, yo empiezo por DI
En proyectos Swift actuales, yo empiezo por DI

Entonces, ¿cuándo conviene usarlo?

En resumen, en estos casos.

  1. Cuando hay demasiado código existente basado en Singleton y quieres retirarlo gradualmente
  2. Cuando quieres mantener limpia y similar a la de un objeto normal la interfaz del código cliente
  3. Cuando necesitas gestionar estado global que requiera herencia o extensión

Por el contrario, si vas a crear un proyecto nuevo desde cero, recomendaría considerar antes la inyección de dependencias o una gestión explícita del estado.

Guardo los patrones en mi caja de herramientas y los saco cuando la situación lo requiere
Guardo los patrones en mi caja de herramientas y los saco cuando la situación lo requiere

En definitiva, Monostate no es “un superconjunto perfecto de Singleton”, sino “otra opción con una personalidad diferente”. Guárdalo en tu caja de herramientas y úsalo cuando encaje.

Espero que el contenido de hoy sirva de pequeña pista a quienes están cansados del abuso de .shared. En lugar de cambiar sin más, piensa qué encaja mejor en tu proyecto.

Seguir leyendo