Go Idioms: Composição Sobre Herança
O código Go parece nativo quando favorece designs de composição em vez de herança.
Busque em todas as páginas da documentação
O código Go parece nativo quando favorece designs de composição em vez de herança.
Isso significa structs pequenas, construtores explícitos, interfaces estreitas e encapsulamento de funções no estilo middleware em vez de hierarquias de classes profundas copiadas de outras linguagens.
Linguagens com herança de classe incentivam modelagem "é um": um CachedUserService é um UserService é um BaseService.
Go omite deliberadamente esse mecanismo.
Uma struct contém dados; métodos anexam comportamento com receptores.
Quando dois tipos precisam de capacidades semelhantes, você embuti outro tipo para promover seus métodos, ou você programa para uma interface compartilhada definida no consumidor.
Embedding se parece com herança, mas se comporta como delegação.
Uma struct externa com um campo anônimo http.Handler tem o ServeHTTP promovido para si mesma, mas o tipo externo não é substituível pelo tipo interno em todos os contextos.
Interfaces são satisfeitas implicitamente: se o seu tipo tem os métodos, ele implementa a interface sem declaração.
Isso habilita o idioma aceitar interfaces, retornar structs - os chamadores dependem do menor comportamento que precisam; seu pacote retorna tipos concretos para que os chamadores não fiquem presos às formas de sua interface.
APIs nativas Go também favorecem funções construtoras (NewServer, OpenDB) que retornam valores inicializados com campos não exportados, mais opções funcionais opcionais para configuração sem assinaturas New(a, b, c, ...) em cascata.
A arquitetura Go do dia a dia empilha primitivas de composição em camadas previsíveis:
Requisição HTTP
│
▼
middleware (func envolvendo func)
│
▼
handler (struct concreta, depende de interface)
│
▼
service (regras de negócio, aceita interface Repository)
│
▼
repository (implementação postgres / mock)Middleware é composição via funções: func(http.Handler) http.Handler envolve comportamento sem subclassing.
Cada camada adiciona logging, autenticação ou métricas, e então chama o handler interno.
As fronteiras de serviço e repositório usam interfaces definidas pelo consumidor (type UserStore interface { Get(ctx, id) (User, error) }) para que os testes troquem fakes sem geração de código.
Opções funcionais compõem a configuração no momento da construção: NewClient(WithTimeout(2*time.Second), WithRetries(3)) constrói um cliente válido sem campos mutáveis exportados.
sync.Once compõe inicialização segura e preguiçosa em um pacote sem armadilhas de ordenação de init().
| Padrão | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
| Pequenas interfaces | Fakes fáceis, testes estáveis | Muitos tipos nomeados | Serviços de domínio, bibliotecas |
| Embedding | Reutilização rápida de padrões | API promovida oculta | Decoradores, wrappers finos |
| Opções funcionais | Construtores extensíveis | Curva de aprendizado leve | Clientes, servidores, SDKs |
| Middleware de função | Cerimônia mínima | Mais difícil com APIs multi-método | Interceptadores HTTP, gRPC |
| Singletons globais | Acesso conveniente | Deps ocultas, dor de teste | Evitar; preferir injeção explícita |
Roteadores de framework (chi, gin, echo) e interceptadores gRPC compõem através de funções e hooks em forma de interface.
Seus pacotes de domínio ainda devem expor interfaces de domínio, não tipos de roteador, para que a troca de infraestrutura não reescreva a lógica de negócios.
Equipes Go maduras codificam regras de composição em checklists de revisão, não em folclore.
Prefira funções simples até que uma segunda implementação apareça; então extraia uma interface no pacote do consumidor.
Mantenha interfaces com um ou dois métodos quando possível - io.Reader, io.Writer e http.Handler são a escala a imitar.
Evite poluição de interface: declarar interfaces ao lado de cada tipo concreto "para testes" frequentemente produz contratos amplos e instáveis.
Use embedding para delegação verdadeira (envolvendo http.ResponseWriter para capturar códigos de status), não para simular hierarquias de herança.
Opções funcionais pertencem a construtores exportados com várias configurações ortogonais.
Não os use para auxiliares internos com dois parâmetros.
Camadas de repositório e serviço são composição na fronteira do pacote: serviços orquestram; repositórios isolam detalhes de SQL/HTTP/SDK.
Controladores permanecem finos.
Anti-padrões a observar: pacotes "god" que misturam HTTP, SQL e regras de negócios; fluxo de controle impulsionado por pânico; init() que conecta bancos de dados de produção; singletons que escondem dependências das assinaturas.
Genéricos do Go 1.18+ reduzem auxiliares de coleção repetitivos, mas não substituem a composição de interface em pontos de integração do sistema.
Alcance os genéricos dentro de algoritmos; mantenha APIs externas com structs e interfaces idiomáticas.
sync.Once protege a inicialização única; injeção de dependência com parâmetros explícitos continua sendo o padrão para testabilidade.Nenhuma herança de classe ou sobrescrita de método em uma hierarquia de tipos.
Go oferece embedding para promoção de métodos e polimorfismo de interface para substituição de comportamento.
Embuta quando quiser métodos promovidos em um decorador fino (por exemplo, envolvendo http.Handler).
Envolva em um campo nomeado quando precisar ocultar ou expor seletivamente o comportamento interno.
Frequentemente um método para limites de infraestrutura (Store, Publisher, Clock).
Adicione métodos apenas quando os chamadores realmente precisarem deles juntos.
Defina-as onde são consumidas (o pacote que chama o comportamento).
Implementações em outros pacotes as satisfazem implicitamente.
São comuns em pacotes estilo stdlib (grpc, otel), mas opcionais.
Use-as quando construtores se espalhariam por muitos parâmetros.
Passe interfaces ou parâmetros de função para construtores.
Testes fornecem fakes sem subclassificar tipos de produção.
Em Go, sim na prática: uma função envolve um http.Handler (ou handler gRPC) para adicionar comportamento transversal antes de delegar para dentro.
Quando serviços de negócios precisam de testes estáveis sem um banco de dados, ou quando o armazenamento pode mudar (Postgres, cache, API remota).
Estado mutável em nível de pacote esconde dependências, complica testes paralelos e incentiva bugs de ordem de inicialização.
Prefira injeção explícita de construtor.
Genéricos ajudam algoritmos compartilhados sobre tipos comparáveis.
Polimorfismo de tempo de execução e pontos de integração externos ainda dependem de interfaces.
Hierarquias estilo Java/C#, campos de struct exportados em tipos de biblioteca, interfaces amplas e panic para erros esperados são sinais comuns.
Comece com a composição mais simples que resolve o problema.
Alcance opções, repositórios ou middleware quando um segundo eixo de mudança aparecer.
Versões de Stack: Esta página foi escrita para Go 1.26.x (padrão Green Tea GC, go fix modernizers - verifique o patch na compilação), chi (última versão - verifique na compilação), gin (última versão - verifique na compilação), echo (última versão - verifique na compilação), google.golang.org/grpc (última versão - verifique na compilação), sigs.k8s.io/controller-runtime (última versão - verifique na compilação), kubebuilder (última versão - verifique na compilação), tinygo (última versão - verifique os alvos de placa na compilação), wazero (última versão - verifique na compilação) e golangci-lint (última versão - verifique na compilação).
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026