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.

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.
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 y hoy estándar en todo el ecosistema de Go, usa argumentos variádicos para configurar un struct.
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.
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.
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.
¿Listo para aprobar tus entrevistas de Go?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
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 de Go y evita el enredo de callbacks.
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.
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, 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. Los candidatos que confunden embedding con herencia revelan una comprensión superficial del sistema de tipos de 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.
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.
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.
¿Listo para aprobar tus entrevistas de Go?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
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
selectpara 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
¡Empieza a practicar!
Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.
Etiquetas
Compartir
Artículos relacionados

Preguntas de Entrevista sobre Go 1.26: Green Tea GC, go fix y Nuevas Optimizaciones
Guía completa sobre las características de Go 1.26 para entrevistas técnicas, incluyendo el recolector de basura Green Tea, optimizaciones de stack y nuevas herramientas del lenguaje.

Entrevista Tecnica de Go: Goroutines, Channels y Concurrencia
Guia completa con las preguntas mas frecuentes sobre goroutines, channels y concurrencia en Go. Incluye ejemplos de codigo listos para produccion, patrones de concurrencia y las respuestas que los entrevistadores esperan en 2026.

Top 25 preguntas de entrevista Go: guía completa para desarrolladores
Domina las entrevistas de Go con las 25 preguntas más frecuentes. Goroutines, channels, interfaces y patrones de concurrencia con ejemplos de código.