Patrones de diseño en Go: no una «religión», sino una herramienta
\n\nCuando empecé a escribir en Go, me perseguía la sensación de que todo eso de la «banda de los cuatro» y sus 23 patrones era algo del mundo de Java y C++. Go es tan simple y conciso — ¿para qué necesitamos estas complejidades? Pero cuanto más me sumergía en proyectos reales (microservicios, utilidades CLI, manejadores de API), más a menudo me sorprendía pensando: «Aquí vendría bien un método fábrica» o «Esta montaña de if-else se puede reemplazar con una estrategia».
\n\nLos patrones de diseño en Go no son un dogma ni un intento de forzar la POO en un lenguaje funcional. Son soluciones probadas para problemas típicos. Ayudan a que el código sea comprobable, flexible y comprensible para los colegas. Y sí, Go deja su huella: debido a la ausencia de herencia clásica y genéricos (hasta la versión 1.18), algunos patrones se transforman y otros se vuelven redundantes.
\n\nEn este artículo no enumeraremos los 23 patrones. En su lugar, analizaremos los 5 patrones más demandados en Golang que realmente serán útiles en proyectos reales. Verá cuándo aplicarlos y cuándo salir corriendo sin mirar atrás.
\n\nPatrón Singleton en Go: no solo un singleton, sino un singleton a la manera de Go
\n\nSingleton garantiza que solo haya una instancia de la estructura. En Go, esto se usa a menudo para configuraciones, grupos de conexiones o registradores. Pero hay un matiz: en Go no hay clases estáticas, pero sí sync.Once — la herramienta ideal para la inicialización diferida.
Problema sin Singleton
\nImagine que tiene una estructura Config que lee la configuración de un archivo. Si cada paquete crea su propia instancia, leerá el archivo 10 veces (lento) o tendrá una condición de carrera.
Solución con Singleton
\npackage config\n\nimport (\n "sync"\n "encoding/json"\n "os"\n)\n\ntype Config struct {\n Port int `json:"port"`\n Env string `json:"env"`\n}\n\nvar (\n instance *Config\n once sync.Once\n)\n\nfunc GetConfig() *Config {\n once.Do(func() {\n file, _ := os.Open("config.json")\n defer file.Close()\n instance = &Config{}\n json.NewDecoder(file).Decode(instance)\n })\n return instance\n}\n\nCuándo usarlo: cuando se necesita garantizar la unicidad de la instancia (configuraciones, cachés, fábricas).
\nCuándo no usarlo: para todo. Singleton es un estado global que dificulta las pruebas. Si se puede pasar la dependencia a través del constructor — pásela.
\n\nPatrón Factory: cuando la creación de objetos se vuelve compleja
\n\nFactory Method y Abstract Factory tratan sobre encapsular la lógica de creación de objetos. En Go, esto es especialmente relevante ya que no tenemos constructores en el sentido clásico. En su lugar, usamos funciones constructoras con el prefijo New.
Ejemplo: creación de notificaciones
\nSupongamos que su sistema envía notificaciones por Email, SMS y Push. En lugar de dispersar la lógica de selección por el código, usamos una fábrica.
\n\npackage notification\n\nimport "fmt"\n\ntype Notifier interface {\n Send(message string) error\n}\n\ntype EmailNotifier struct{}\n\nfunc (e EmailNotifier) Send(message string) error {\n fmt.Println("Enviando email:", message)\n return nil\n}\n\ntype SMSNotifier struct{}\n\nfunc (s SMSNotifier) Send(message string) error {\n fmt.Println("Enviando SMS:", message)\n return nil\n}\n\nfunc NewNotifier(typ string) Notifier {\n switch typ {\n case "email":\n return EmailNotifier{}\n case "sms":\n return SMSNotifier{}\n default:\n return nil\n }\n}\n\nCuándo usarlo: cuando la lógica de creación es compleja (depende del tipo, versión, configuración).
\nCuándo no usarlo: si solo tiene un tipo de objeto. Simplemente escriba MyStruct{} o NewMyStruct() sin fábrica.