Design patterns em Go: patterns essenciais e perguntas de entrevista para desenvolvedores Go
Domine os design patterns de Go: Functional Options, Strategy, Factory e Observer. Exemplos de código práticos, boas práticas idiomáticas e perguntas de entrevista frequentes para desenvolvedores Go.

Os design patterns em Go diferem de forma fundamental de seus equivalentes nas linguagens orientadas a objetos. Sem classes ou herança, Go se apoia em composição, interfaces e funções de primeira classe para alcançar a mesma flexibilidade estrutural, muitas vezes com menos cerimônia e mais clareza.
Go favorece a composição em vez da herança. Cada design pattern clássico precisa ser adaptado para funcionar com interfaces, embedding de structs e funções como cidadãs de primeira classe, em vez de hierarquias de classes.
O pattern Functional Options para configuração flexível
O pattern Functional Options resolve um problema comum em Go: construir objetos com muitos parâmetros opcionais. Diferentemente das linguagens com sobrecarga de métodos ou argumentos padrão, Go não oferece nenhum dos dois. O pattern Builder funciona, mas soa verboso. As Functional Options fornecem uma API limpa e extensível que permanece retrocompatível conforme os requisitos crescem.
Esse pattern, popularizado por Dave Cheney e hoje padrão em todo o ecossistema Go, usa argumentos variádicos para configurar um 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
}Quem chama especifica apenas o que difere dos valores padrão. Adicionar uma nova opção nunca quebra os pontos de chamada existentes. Esse pattern aparece em bibliotecas de produção como google.golang.org/grpc e go.uber.org/zap.
O pattern Strategy pela satisfação de interfaces
O pattern Strategy encapsula algoritmos intercambiáveis atrás de uma interface comum. Em Go, isso se traduz diretamente em polimorfismo baseado em interfaces, sem classes abstratas nem cadeias de herança.
Um sistema de processamento de pagamentos ilustra o pattern. Cada forma de pagamento implementa a mesma interface, e o serviço de checkout escolhe a estratégia em tempo de execução.
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
}As interfaces de Go são satisfeitas de forma implícita, sem a palavra-chave implements. Qualquer tipo com um método Pay(float64) (string, error) se qualifica como Processor. Isso mantém o acoplamento baixo e simplifica os testes: basta passar um Processor simulado que retorne resultados previsíveis.
Funções factory e patterns de construção em Go
Go não tem construtores. A substituição idiomática é uma função factory, normalmente chamada New ou NewXxx, que retorna um struct inicializado. As funções factory impõem invariantes que a inicialização por valor zero não consegue garantir.
A distinção importa para a preparação de entrevistas: a pessoa que desenvolve em Go deve reconhecer quando uma função factory é necessária e quando o valor zero é útil por padrão.
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
}A função factory NewConnPool valida o parâmetro maxSize e pré-aloca o slice. A inicialização direta do struct (&ConnPool{}) pularia essa validação e poderia gerar panics em tempo de execução. Esse pattern se estende naturalmente: combina-se com as Functional Options para cenários de configuração mais complexos.
Pronto para mandar bem nas entrevistas de Go?
Pratique com nossos simuladores interativos, flashcards e testes tecnicos.
O pattern Observer com channels e goroutines
O pattern Observer notifica vários assinantes quando o estado muda. Em Java ou C#, isso envolve listeners de eventos e registro de callbacks. Go oferece um caminho mais idiomático: os channels. Cada assinante recebe os eventos por seu próprio channel, e as goroutines cuidam da entrega de forma concorrente.
Essa abordagem aproveita diretamente as primitivas de concorrência de Go e evita o emaranhado 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
}
}
}O select com uma cláusula default impede que um assinante lento bloqueie o bus inteiro. Em sistemas de produção, adicionar um mecanismo de cancelamento baseado em contexto e um método Unsubscribe completaria a implementação.
O pattern Middleware para pipelines de requisições HTTP
As cadeias de middleware são a espinha dorsal dos servidores HTTP em Go. O pattern envolve um http.Handler com comportamento adicional (logging, autenticação, limitação de taxa) sem modificar o próprio handler. A interface http.Handler da biblioteca padrão torna essa composição 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
}A função Chain aplica os middleware na ordem de declaração. Cada middleware envolve o próximo, formando um pipeline. Esse pattern é idêntico em routers populares como o chi, que aceita func(http.Handler) http.Handler como middleware.
Composição em vez de herança com embedding de structs
Go não dá suporte a herança. Em vez disso, o embedding de structs promove os métodos de um tipo interno para o tipo externo, alcançando reutilização de código sem acoplamento forte. Isso não é herança: o tipo embutido não tem conhecimento do tipo que o embute, e não há despacho virtual.
Compreender essa distinção é crucial para as perguntas de entrevista de Go. Quem confunde embedding com herança revela uma compreensão superficial do 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
}Agora tanto User quanto Order expõem diretamente os campos CreatedAt e UpdatedAt. Quaisquer métodos definidos em Timestamps também são promovidos. A regra principal: o embedding fornece delegação, não substituição. Um User não é um Timestamps; ele tem um Timestamps.
Perguntas de entrevista sobre design patterns em Go
As perguntas sobre design patterns nas entrevistas de Go avaliam se a pessoa candidata sabe adaptar os patterns clássicos ao sistema de tipos de Go. As perguntas a seguir aparecem com frequência nas triagens técnicas e nas rodadas presenciais.
P: Quando uma função factory deve retornar um erro em vez de dar panic?
Uma função factory deve retornar um erro sempre que a validação depender de entrada em tempo de execução (configuração fornecida pelo usuário, variáveis de ambiente, dados externos). O panic fica reservado para erros de programação, ou seja, situações que indicam um bug, como passar um ponteiro nil onde o contrato da API proíbe. A biblioteca padrão segue essa convenção: os.Open retorna um erro, enquanto regexp.MustCompile dá panic porque espera um padrão constante em tempo de compilação.
P: Como o pattern Functional Options melhora a evolução de uma API?
Adicionar uma nova função WithXxx é uma mudança não disruptiva. Quem já chamava a API continua funcionando sem alterações. Isso contrasta com os structs de configuração, em que adicionar um campo obrigatório quebra todos os pontos de chamada, ou com os parâmetros posicionais, em que reordenar os argumentos introduz bugs.
P: O que diferencia as interfaces de Go das de Java ou C#?
As interfaces de Go são satisfeitas de forma implícita. Um tipo implementa uma interface apenas por ter os métodos exigidos, sem necessidade de uma declaração implements. Isso habilita o princípio "aceite interfaces, retorne structs": definir interfaces estreitas no ponto de chamada, e não no de implementação. O resultado é menor acoplamento e maior testabilidade.
Quem entrevista costuma pedir que a pessoa candidata refatore uma dependência concreta em uma interface. O teste: ela sabe identificar o conjunto mínimo de métodos necessário e extraí-lo no lado do consumidor em vez do produtor?
P: Em que um Observer baseado em channels difere de um baseado em callbacks?
Os channels desacoplam o produtor e o assinante tanto no tempo quanto no espaço. O produtor não mantém referências às funções dos assinantes: ele envia para channels. Os assinantes podem processar os eventos no próprio ritmo usando channels com buffer. A instrução select habilita o tratamento de timeouts, o cancelamento via context e a multiplexação entre várias fontes de eventos, capacidades que os callbacks não oferecem de graça.
Esquecer de fechar os channels dos assinantes causa vazamentos de goroutines. Cada Subscribe deve ter um Unsubscribe correspondente que feche o channel e o remova do bus.
Pronto para mandar bem nas entrevistas de Go?
Pratique com nossos simuladores interativos, flashcards e testes tecnicos.
Conclusão
- O pattern Functional Options substitui builders e structs de configuração por uma API limpa e extensível que nunca quebra quem já chama
- A estratégia em Go se traduz em polimorfismo baseado em interfaces: definir a interface no lado do consumidor, não no do fornecedor
- As funções factory impõem invariantes que a inicialização por valor zero não garante; retornar erros para entrada em tempo de execução e dar panic apenas em bugs de programação
- O Observer baseado em channels aproveita goroutines e
selectpara uma entrega de eventos concorrente e desacoplada, sem cadeias de callbacks - As cadeias de middleware HTTP se compõem pela assinatura
func(http.Handler) http.Handler, idêntica na biblioteca padrão e nos routers de terceiros - O embedding de structs fornece delegação, não herança: compreender essa distinção separa quem domina Go de quem traduz patterns de Java ao pé da letra
- Passar em uma entrevista exige demonstrar uma adaptação idiomática dos patterns ao sistema de tipos de Go, e não uma recitação de manual das definições do Gang of Four
Comece a praticar!
Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.
Tags
Compartilhar
Artigos relacionados

Perguntas de Entrevista sobre Go 1.26: Green Tea GC, go fix e Novos Recursos
Guia completo sobre os novos recursos do Go 1.26 para entrevistas técnicas, incluindo Green Tea GC, otimizações de stack para slices, go fix modernizado e melhorias em generics.

Entrevista Tecnica de Go: Goroutines, Channels e Concorrencia
Perguntas de entrevista tecnica sobre goroutines, channels e padroes de concorrencia em Go. Exemplos de codigo, armadilhas comuns e respostas de nivel avancado para se preparar para entrevistas Go em 2026.

Top 25 perguntas de entrevista Go: guia completo do desenvolvedor
Domine as entrevistas de Go com as 25 perguntas mais frequentes. Goroutines, channels, interfaces e padrões de concorrência com exemplos de código.