# 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. - Published: 2026-05-28 - Updated: 2026-06-06 - Author: SharpSkill - Tags: go, design-patterns, best-practices, interview, software-architecture - Reading time: 10 min --- 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. > **Princípio idiomático de Go** > > 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](https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis) e hoje padrão em todo o ecossistema Go, usa argumentos variádicos para configurar um 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 } ``` 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. ```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 } ``` 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](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 } ``` 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. ## 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](/blog/go/go-concurrency-goroutines-channels) de Go e evita o emaranhado 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 } } } ``` 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. ```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 } ``` 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](https://github.com/go-chi/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](/technologies/go/interview-questions/concurrency-patterns). Quem confunde embedding com herança revela uma compreensão superficial do 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 } ``` 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](/technologies/go/interview-questions/testing). > **Padrão de entrevista** > > 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. > **Armadilha comum** > > 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. ## 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 `select` para 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 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pt/blog/go/go-design-patterns-essential-patterns-interview