# Patrones de diseño en Go: patrones esenciales y preguntas de entrevista para desarrolladores Go > Domina los patrones de diseño de Go: Functional Options, Strategy, Factory y Observer. Ejemplos de código prácticos, buenas prácticas idiomáticas y preguntas de entrevista frecuentes para desarrolladores Go. - Published: 2026-05-28 - Updated: 2026-06-06 - Author: SharpSkill - Tags: go, design-patterns, best-practices, interview, software-architecture - Reading time: 10 min --- Los patrones de diseño en Go difieren de forma fundamental de sus equivalentes en los lenguajes orientados a objetos. Sin clases ni herencia, Go se apoya en la composición, las interfaces y las funciones de primera clase para lograr la misma flexibilidad estructural, a menudo con menos ceremonia y más claridad. > **Principio idiomático de Go** > > Go prefiere la composición sobre la herencia. Cada patrón de diseño clásico debe adaptarse para funcionar con interfaces, embedding de structs y funciones como ciudadanos de primera clase, en lugar de jerarquías de clases. ## El patrón Functional Options para una configuración flexible El patrón Functional Options resuelve un problema común en Go: construir objetos con muchos parámetros opcionales. A diferencia de los lenguajes con sobrecarga de métodos o argumentos por defecto, Go no ofrece ninguno de los dos. El patrón Builder funciona, pero resulta verboso. Las Functional Options proporcionan una API limpia y extensible que se mantiene retrocompatible a medida que crecen los requisitos. Este patrón, [popularizado por Dave Cheney](https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis) y hoy estándar en todo el ecosistema de Go, usa argumentos variádicos para configurar un struct. ```go // server.go package server import ( "time" "log/slog" ) // Server holds the HTTP server configuration. type Server struct { host string port int timeout time.Duration maxConns int logger *slog.Logger } // Option defines a functional option for Server. type Option func(*Server) // WithPort sets the server port. func WithPort(port int) Option { return func(s *Server) { s.port = port } } // WithTimeout sets the request timeout. func WithTimeout(d time.Duration) Option { return func(s *Server) { s.timeout = d } } // WithMaxConns sets the maximum concurrent connections. func WithMaxConns(n int) Option { return func(s *Server) { s.maxConns = n } } // New creates a Server with sensible defaults and applies options. func New(host string, opts ...Option) *Server { srv := &Server{ host: host, port: 8080, // default port timeout: 30 * time.Second, // default timeout maxConns: 100, // default max connections logger: slog.Default(), } for _, opt := range opts { opt(srv) } return srv } ``` Quien llama solo especifica lo que difiere de los valores por defecto. Agregar una nueva opción nunca rompe los puntos de llamada existentes. Este patrón aparece en bibliotecas de producción como `google.golang.org/grpc` y `go.uber.org/zap`. ## El patrón Strategy mediante la satisfacción de interfaces El patrón Strategy encapsula algoritmos intercambiables detrás de una interfaz común. En Go, esto se traduce directamente en polimorfismo basado en interfaces, sin clases abstractas ni cadenas de herencia. Un sistema de procesamiento de pagos ilustra el patrón. Cada método de pago implementa la misma interfaz, y el servicio de checkout selecciona la estrategia en tiempo de ejecución. ```go // payment.go package payment import "fmt" // Processor defines the strategy interface. type Processor interface { Pay(amount float64) (string, error) } // CreditCard implements Processor for card payments. type CreditCard struct { CardNumber string Expiry string } func (c *CreditCard) Pay(amount float64) (string, error) { // Charge the card via payment gateway return fmt.Sprintf("charged %.2f to card ending %s", amount, c.CardNumber[len(c.CardNumber)-4:]), nil } // BankTransfer implements Processor for wire transfers. type BankTransfer struct { IBAN string } func (b *BankTransfer) Pay(amount float64) (string, error) { return fmt.Sprintf("initiated transfer of %.2f to %s", amount, b.IBAN), nil } // Checkout processes a payment using the given strategy. func Checkout(p Processor, amount float64) error { receipt, err := p.Pay(amount) if err != nil { return fmt.Errorf("payment failed: %w", err) } fmt.Println(receipt) return nil } ``` Las interfaces de Go se satisfacen de forma implícita, sin la palabra clave `implements`. Cualquier tipo con un método `Pay(float64) (string, error)` califica como `Processor`. Esto mantiene bajo el acoplamiento y simplifica las pruebas: basta con pasar un `Processor` simulado que devuelva resultados predecibles. ## Funciones factory y patrones de construcción en Go Go no tiene constructores. El reemplazo idiomático es una función factory, normalmente llamada `New` o `NewXxx`, que devuelve un struct inicializado. Las funciones factory imponen invariantes que la inicialización con valor cero no puede garantizar. La distinción importa para la preparación de entrevistas: un desarrollador de Go debe reconocer cuándo una función factory es necesaria frente a los casos en que el [valor cero es útil por defecto](https://go.dev/blog/organizing-go-code). ```go // connpool.go package pool import ( "errors" "sync" ) // ConnPool manages a pool of reusable connections. type ConnPool struct { mu sync.Mutex conns []Conn maxSize int } // Conn represents a database connection. type Conn struct { ID int Active bool } // NewConnPool validates parameters and returns an initialized pool. func NewConnPool(maxSize int) (*ConnPool, error) { if maxSize <= 0 { return nil, errors.New("pool: maxSize must be positive") } return &ConnPool{ conns: make([]Conn, 0, maxSize), maxSize: maxSize, }, nil } // Acquire returns a connection from the pool. func (p *ConnPool) Acquire() (*Conn, error) { p.mu.Lock() defer p.mu.Unlock() for i := range p.conns { if !p.conns[i].Active { p.conns[i].Active = true return &p.conns[i], nil } } if len(p.conns) >= p.maxSize { return nil, errors.New("pool: no available connections") } c := Conn{ID: len(p.conns) + 1, Active: true} p.conns = append(p.conns, c) return &p.conns[len(p.conns)-1], nil } ``` La función factory `NewConnPool` valida el parámetro `maxSize` y preasigna el slice. La inicialización directa del struct (`&ConnPool{}`) saltaría esa validación y podría provocar panics en tiempo de ejecución. Este patrón se extiende con naturalidad: se combina con las Functional Options para escenarios de configuración más complejos. ## El patrón Observer con channels y goroutines El patrón Observer notifica a varios suscriptores cuando cambia el estado. En Java o C#, esto implica escuchadores de eventos y registro de callbacks. Go ofrece un camino más idiomático: los channels. Cada suscriptor recibe los eventos a través de su propio channel, y las goroutines gestionan la entrega de forma concurrente. Este enfoque aprovecha directamente las [primitivas de concurrencia](/blog/go/go-concurrency-goroutines-channels) de Go y evita el enredo de callbacks. ```go // eventbus.go package events import "sync" // Event carries a topic and payload. type Event struct { Topic string Payload any } // Bus manages subscriptions and event dispatch. type Bus struct { mu sync.RWMutex subscribers map[string][]chan Event } // NewBus creates an event bus. func NewBus() *Bus { return &Bus{ subscribers: make(map[string][]chan Event), } } // Subscribe returns a channel that receives events for a topic. func (b *Bus) Subscribe(topic string) <-chan Event { ch := make(chan Event, 16) // buffered to avoid blocking publisher b.mu.Lock() b.subscribers[topic] = append(b.subscribers[topic], ch) b.mu.Unlock() return ch } // Publish sends an event to all subscribers of the topic. func (b *Bus) Publish(topic string, payload any) { b.mu.RLock() defer b.mu.RUnlock() for _, ch := range b.subscribers[topic] { select { case ch <- Event{Topic: topic, Payload: payload}: default: // subscriber too slow, drop event } } } ``` El `select` con una cláusula `default` evita que un suscriptor lento bloquee todo el bus. En sistemas de producción, agregar un mecanismo de cancelación basado en contexto y un método `Unsubscribe` completaría la implementación. ## El patrón Middleware para pipelines de peticiones HTTP Las cadenas de middleware son la columna vertebral de los servidores HTTP en Go. El patrón envuelve un `http.Handler` con comportamiento adicional (registro, autenticación, limitación de tasa) sin modificar el handler en sí. La interfaz `http.Handler` de la biblioteca estándar hace que esta composición sea trivial. ```go // middleware.go package middleware import ( "log/slog" "net/http" "time" ) // Logging records request duration and status. func Logging(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() wrapped := &statusWriter{ResponseWriter: w, status: 200} next.ServeHTTP(wrapped, r) slog.Info("request", "method", r.Method, "path", r.URL.Path, "status", wrapped.status, "duration", time.Since(start), ) }) } // statusWriter captures the HTTP status code. type statusWriter struct { http.ResponseWriter status int } func (w *statusWriter) WriteHeader(code int) { w.status = code w.ResponseWriter.WriteHeader(code) } // Chain applies middleware in order: first listed = outermost. func Chain(h http.Handler, mw ...func(http.Handler) http.Handler) http.Handler { for i := len(mw) - 1; i >= 0; i-- { h = mw[i](h) } return h } ``` La función `Chain` aplica los middleware en el orden de declaración. Cada middleware envuelve al siguiente, formando un pipeline. Este patrón es idéntico en [routers populares como chi](https://github.com/go-chi/chi), que acepta `func(http.Handler) http.Handler` como middleware. ## Composición sobre herencia con embedding de structs Go no admite herencia. En su lugar, el embedding de structs promueve los métodos de un tipo interno hacia el tipo externo, logrando la reutilización de código sin acoplamiento fuerte. Esto no es herencia: el tipo embebido no tiene conocimiento del tipo que lo embebe, y no hay despacho virtual. Comprender esta distinción es crucial para las [preguntas de entrevista de Go](/technologies/go/interview-questions/concurrency-patterns). Los candidatos que confunden embedding con herencia revelan una comprensión superficial del sistema de tipos de Go. ```go // models.go package models import "time" // Timestamps provides common audit fields. type Timestamps struct { CreatedAt time.Time UpdatedAt time.Time } // User embeds Timestamps to gain CreatedAt/UpdatedAt. type User struct { Timestamps ID int Email string } // Order also embeds Timestamps. type Order struct { Timestamps ID int UserID int Total float64 } ``` Ahora tanto `User` como `Order` exponen directamente los campos `CreatedAt` y `UpdatedAt`. Cualquier método definido en `Timestamps` también se promueve. La regla clave: el embedding proporciona delegación, no sustitución. Un `User` no es un `Timestamps`; tiene un `Timestamps`. ## Preguntas de entrevista sobre patrones de diseño en Go Las preguntas sobre patrones de diseño en las entrevistas de Go evalúan si un candidato sabe adaptar los patrones clásicos al sistema de tipos de Go. Las siguientes preguntas aparecen con frecuencia en las cribas técnicas y en las rondas presenciales. **P: ¿Cuándo debe una función factory devolver un error en lugar de hacer panic?** Una función factory debe devolver un error siempre que la validación dependa de la entrada en tiempo de ejecución (configuración proporcionada por el usuario, variables de entorno, datos externos). El panic se reserva para los errores de programación, es decir, situaciones que indican un bug, como pasar un puntero nil donde el contrato de la API lo prohíbe. La biblioteca estándar sigue esta convención: `os.Open` devuelve un error, mientras que `regexp.MustCompile` hace panic porque espera un patrón constante en tiempo de compilación. **P: ¿Cómo mejora el patrón Functional Options la evolución de una API?** Agregar una nueva función `WithXxx` es un cambio no disruptivo. Quienes ya llamaban a la API siguen funcionando sin modificaciones. Esto contrasta con los structs de configuración, donde agregar un campo obligatorio rompe todos los puntos de llamada, o con los parámetros posicionales, donde reordenar los argumentos introduce bugs. **P: ¿Qué diferencia a las interfaces de Go de las de Java o C#?** Las interfaces de Go se satisfacen de forma implícita. Un tipo implementa una interfaz simplemente con tener los métodos requeridos, sin necesidad de una declaración `implements`. Esto habilita el principio «acepta interfaces, devuelve structs»: definir interfaces estrechas en el punto de llamada, no en el de implementación. El resultado es un [menor acoplamiento y una mayor testabilidad](/technologies/go/interview-questions/testing). > **Patrón de entrevista** > > Quienes entrevistan suelen pedir a los candidatos que refactoricen una dependencia concreta en una interfaz. La prueba: ¿sabe el candidato identificar el conjunto mínimo de métodos necesario y extraerlo del lado del consumidor en lugar del lado del productor? **P: ¿En qué se diferencia un Observer basado en channels de uno basado en callbacks?** Los channels desacoplan al productor y al suscriptor tanto en el tiempo como en el espacio. El productor no mantiene referencias a las funciones de los suscriptores: envía a channels. Los suscriptores pueden procesar los eventos a su propio ritmo gracias a los channels con buffer. La instrucción `select` habilita el manejo de tiempos de espera, la cancelación mediante `context` y el multiplexado entre varias fuentes de eventos, capacidades que los callbacks no ofrecen de forma gratuita. > **Error frecuente** > > Olvidar cerrar los channels de los suscriptores provoca fugas de goroutines. Cada `Subscribe` debe tener su `Unsubscribe` correspondiente que cierre el channel y lo elimine del bus. ## Conclusión - El patrón Functional Options reemplaza los builders y los structs de configuración por una API limpia y extensible que nunca rompe a quienes ya llamaban - La estrategia en Go se traduce en polimorfismo basado en interfaces: definir la interfaz del lado del consumidor, no del proveedor - Las funciones factory imponen invariantes que la inicialización con valor cero no puede garantizar; devolver errores para la entrada en tiempo de ejecución y hacer panic solo ante bugs de programación - El Observer basado en channels aprovecha las goroutines y `select` para una entrega de eventos concurrente y desacoplada, sin cadenas de callbacks - Las cadenas de middleware HTTP se componen mediante la firma `func(http.Handler) http.Handler`, idéntica en la biblioteca estándar y en los routers de terceros - El embedding de structs proporciona delegación, no herencia: comprender esta distinción separa a quienes dominan Go de quienes traducen patrones de Java al pie de la letra - Aprobar una entrevista exige demostrar una adaptación idiomática de los patrones al sistema de tipos de Go, y no una recitación de manual de las definiciones del Gang of Four --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/es/blog/go/go-design-patterns-essential-patterns-interview