Patrones de diseño en Go con ejemplos: de la A a la Z
Los patrones de diseño (Design Patterns) son soluciones arquitectónicas probadas a lo largo del tiempo que ayudan a los desarrolladores de Go a crear código flexible, escalable y mantenible. A diferencia de los lenguajes clásicos de POO, Go ofrece su propio enfoque único para implementar patrones a través de interfaces, composición y gorutinas. En este artículo analizaremos los principales patrones de diseño en Go con ejemplos de código concretos.
1. Patrones creacionales (Creational Patterns)
Los patrones creacionales se encargan de la creación de objetos, haciendo que el sistema sea independiente de la forma en que se crean.
1.1. Singleton (Singleton)
Singleton garantiza que una clase tenga solo una instancia. En Go, esto se implementa a menudo mediante sync.Once para la seguridad en concurrencia.
package main
import ( "fmt" "sync")
type Database struct { connection string}
var ( instance *Database once sync.Once)
func GetDatabaseInstance() *Database { once.Do(func() { instance = &Database{connection: "postgres://localhost:5432/mydb"} fmt.Println("Creada única conexión a la BD") }) return instance}
func main() { db1 := GetDatabaseInstance() db2 := GetDatabaseInstance() fmt.Println(db1 == db2) // true}1.2. Factory Method (Método Fábrica)
La fábrica delega la creación de objetos a las subclases. En Go, esto se hace mediante interfaces.
package main
import "fmt"
// Interfaz del productotype Transport interface { Deliver() string}
// Productos concretostype Truck struct{}func (t Truck) Deliver() string { return "Entrega por tierra" }
type Ship struct{}func (s Ship) Deliver() string { return "Entrega por mar" }
// Fábricatype LogisticsFactory interface { CreateTransport() Transport}
type RoadLogistics struct{}func (r RoadLogistics) CreateTransport() Transport { return Truck{} }
type SeaLogistics struct{}func (s SeaLogistics) CreateTransport() Transport { return Ship{} }
func main() { var factory LogisticsFactory = RoadLogistics{} transport := factory.CreateTransport() fmt.Println(transport.Deliver()) // Entrega por tierra}2. Patrones estructurales (Structural Patterns)
Los patrones estructurales ayudan a organizar clases y objetos en estructuras más grandes.
2.1. Adapter (Adaptador)
El adaptador permite que objetos con interfaces incompatibles trabajen juntos. En Go, esto se resuelve mediante un envoltorio.
package main
import "fmt"
// Interfaz antiguatype OldPrinter interface { PrintOld(data string)}
type LegacyPrinter struct{}func (l LegacyPrinter) PrintOld(data string) { fmt.Println("Legacy:", data)}
// Nueva interfaztype NewPrinter interface { PrintNew(data string)}
// Adaptadortype PrinterAdapter struct { OldPrinter OldPrinter}
func (a PrinterAdapter) PrintNew(data string) { a.OldPrinter.PrintOld(data)}
func main() { legacy := LegacyPrinter{} adapter := PrinterAdapter{OldPrinter: legacy} adapter.PrintNew("¡Hola, Go!") // Legacy: ¡Hola, Go!}2.2. Composite (Compuesto)
El compuesto permite tratar grupos de objetos como un solo objeto. Ideal para estructuras de árbol.
package main
import "fmt"
// Interfaz del componentetype Graphic interface { Draw() string}
// Elemento hojatype Circle struct{}func (c Circle) Draw() string { return "Dibujamos un círculo" }
// Contenedortype CompoundGraphic struct { children []Graphic}
func (cg *CompoundGraphic) Add(g Graphic) { cg.children = append(cg.children, g)}
func (cg *CompoundGraphic) Draw() string { result := "Figura compuesta:" for _, child := range cg.children { result += " - " + c