
Singleton en Go
Singleton es un patrón de diseño creacional que garantiza que tan solo exista un objeto de su tipo y proporciona un único punto de acceso a él para cualquier otro código.
El patrón tiene prácticamente los mismos pros y contras que las variables globales. Aunque son muy útiles, rompen la modularidad de tu código.
No se puede utilizar una clase que dependa del Singleton en otro contexto. Tendrás que llevar también la clase Singleton. La mayoría de las veces, esta limitación aparece durante la creación de pruebas de unidad.
Ejemplo conceptual
Normalmente, se crea una instancia singleton cuando la estructura se inicializa por primera vez. Para hacer que esto suceda, definimos el método getInstance
en la estructura. El método será responsable de crear y devolver la instancia singleton. Una vez creada, la misma instancia singleton será devuelta cada vez que se invoque el getInstance
.
¿Qué pasa con las goroutines? La estructura singleton debe devolver la misma instancia siempre que varias goroutines intenten acceder a esa instancia. Por este motivo, resulta muy sencillo implementar mal el patrón de diseño singleton. El siguiente ejemplo ilustra el modo correcto de crear un singleton.
Algunos puntos a tener en cuenta:
-
Hay una comprobación
nil
al principio para asegurarnos de quesingleInstance
está vacío la primera vez. Esto es para evitar costosas operaciones de bloqueo cada vez que se invoque el métodogetinstance
. Si falla esta comprobación, significa que el camposingleInstance
ya está poblado. -
La estructura
singleInstance
se crea dentro del bloqueo. -
Hay otra comprobación
nil
tras la adquisición del bloqueo. Con esto se garantiza que, si más de una goroutine pasa la primera comprobación, tan solo una de ellas puede crear la instancia singleton. De lo contrario, todas las goroutines crearán sus propias instancias de la estructura singleton.
single.go: Singleton
package main
import (
"fmt"
"sync"
)
var lock = &sync.Mutex{}
type single struct {
}
var singleInstance *single
func getInstance() *single {
if singleInstance == nil {
lock.Lock()
defer lock.Unlock()
if singleInstance == nil {
fmt.Println("Creating single instance now.")
singleInstance = &single{}
} else {
fmt.Println("Single instance already created.")
}
} else {
fmt.Println("Single instance already created.")
}
return singleInstance
}
main.go: Código cliente
package main
import (
"fmt"
)
func main() {
for i := 0; i < 30; i++ {
go getInstance()
}
// Scanln is similar to Scan, but stops scanning at a newline and
// after the final item there must be a newline or EOF.
fmt.Scanln()
}
output.txt: Resultado de la ejecución
Creating single instance now.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Otro ejemplo
Existen otros métodos de creación de instancias de singleton en Go:
- Función
init
Podemos crear una única instancia dentro de la función init
. Esto solo se puede aplicar si la inicialización temprana de la instancia está bien. La función init
se invoca una sola vez por archivo en un paquete, por lo que sabemos con seguridad que solo se creará una instancia.
-
sync.Once
sync.Once
realizará la operación una sola vez. Véase el código a continuación:
syncOnce.go: Singleton
package main
import (
"fmt"
"sync"
)
var once sync.Once
type single struct {
}
var singleInstance *single
func getInstance() *single {
if singleInstance == nil {
once.Do(
func() {
fmt.Println("Creating single instance now.")
singleInstance = &single{}
})
} else {
fmt.Println("Single instance already created.")
}
return singleInstance
}
main.go: Código cliente
package main
import (
"fmt"
)
func main() {
for i := 0; i < 30; i++ {
go getInstance()
}
// Scanln is similar to Scan, but stops scanning at a newline and
// after the final item there must be a newline or EOF.
fmt.Scanln()
}
output.txt: Resultado de la ejecución
Creating single instance now.
Single instance already created.
Single instance already created.