Padrões de Projeto em Go: não uma "religião", mas uma ferramenta
\n\nQuando comecei a programar em Go, me perseguia a sensação de que todos esses "Gang of Four" e seus 23 padrões eram algo do mundo Java e C++. Go é tão simples e conciso — por que precisamos dessas complexidades? Mas quanto mais me aprofundava em projetos reais (microsserviços, utilitários CLI, manipuladores de API), mais me pegava pensando: "Aqui um método fábrica seria útil" ou "Essa montanha de if-else poderia ser substituída por uma estratégia".
\n\nPadrões de projeto em Go não são um dogma nem uma tentativa de encaixar POO em uma linguagem funcional. São soluções comprovadas para problemas típicos. Eles ajudam a tornar o código testável, flexível e compreensível para colegas. E sim, Go impõe suas particularidades: devido à ausência de herança clássica e generics (até a versão 1.18), alguns padrões se transformam, enquanto outros se tornam redundantes.
\n\nNeste artigo, não vamos listar todos os 23 padrões. Em vez disso, analisaremos os 5 padrões mais requisitados em Golang que realmente serão úteis em projetos reais. Você verá quando aplicá-los e quando fugir deles sem olhar para trás.
\n\nPadrão Singleton em Go: não apenas um singleton, mas um singleton ao estilo Go
\n\nSingleton garante que uma estrutura tenha apenas uma instância. Em Go, isso é frequentemente usado para configurações, pools de conexão ou loggers. Mas há um detalhe: em Go não há classes estáticas, mas existe sync.Once — a ferramenta ideal para inicialização preguiçosa.
Problema sem Singleton
\nImagine que você tem uma estrutura Config que lê configurações de um arquivo. Se cada pacote criar sua própria instância, você lerá o arquivo 10 vezes (lento) ou terá uma condição de corrida.
Solução com Singleton
\npackage config\n\nimport (\n "sync"\n "encoding/json"\n "os"\n)\n\ntype Config struct {\n Port int `json:"port"`\n Env string `json:"env"`\n}\n\nvar (\n instance *Config\n once sync.Once\n)\n\nfunc GetConfig() *Config {\n once.Do(func() {\n file, _ := os.Open("config.json")\n defer file.Close()\n instance = &Config{}\n json.NewDecoder(file).Decode(instance)\n })\n return instance\n}\n\nQuando usar: quando for necessária garantia de singularidade da instância (configurações, caches, fábricas).
\nQuando não usar: para tudo. Singleton é um estado global que dificulta os testes. Se for possível passar a dependência pelo construtor — passe.
\n\nPadrão Factory: quando a criação de objetos se torna complexa
\n\nFactory Method e Abstract Factory tratam de encapsular a lógica de criação de objetos. Em Go, isso é especialmente relevante, pois não temos construtores no sentido clássico. Em vez disso, usamos funções construtoras com o prefixo New.
Exemplo: criação de notificações
\nSuponha que seu sistema envie notificações por Email, SMS e Push. Em vez de espalhar a lógica de seleção pelo código, usamos uma fábrica.
\n\npackage notification\n\nimport "fmt"\n\ntype Notifier interface {\n Send(message string) error\n}\n\ntype EmailNotifier struct{}\n\nfunc (e EmailNotifier) Send(message string) error {\n fmt.Println("Enviando email:", message)\n return nil\n}\n\ntype SMSNotifier struct{}\n\nfunc (s SMSNotifier) Send(message string) error {\n fmt.Println("Enviando SMS:", message)\n return nil\n}\n\nfunc NewNotifier(typ string) Notifier {\n switch typ {\n case "email":\n return EmailNotifier{}\n case "sms":\n return SMSNotifier{}\n default:\n return nil\n }\n}\n\nQuando usar: quando a lógica de criação é complexa (depende do tipo, versão, configuração).
\nQuando não usar: se você tem apenas um tipo de objeto. Basta escrever MyStruct{} ou NewMyStruct() sem fábrica.