Padrão Singleton
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 86 de 107.
O padrão Singleton garante que um tipo tenha apenas uma instância em todo o seu programa e fornece um ponto de acesso global a ele. Isso é útil para recursos compartilhados, como gerenciadores de configuração, conexões de banco de dados ou loggers.
Em Go, implementamos o Singleton usando uma variável de nível de pacote combinada com sync.Once para garantir uma inicialização thread-safe:
package config
import "sync"
type Config struct {
DatabaseURL string
MaxRetries int
}
var (
instance *Config
once sync.Once
)
func GetInstance() *Config {
once.Do(func() {
instance = &Config{
DatabaseURL: "localhost:5432",
MaxRetries: 3,
}
})
return instance
}O sync.Once garante que a função de inicialização seja executada exatamente uma vez, mesmo quando múltiplas goroutines chamam GetInstance() simultaneamente. Cada chamada subsequente retorna a mesma instância sem reexecutar o código de inicialização.
cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 e cfg2 apontam para a mesma instânciaUse o Singleton com moderação. Embora conveniente, ele introduz um estado global que pode dificultar os testes e ocultar dependências. Considere se a injeção de dependência pode ser uma alternativa melhor para o seu caso de uso.
Desafio
FácilVamos construir um Singleton Logger que garante que apenas uma instância do logger exista em toda a sua aplicação! Este é um caso de uso clássico para o padrão Singleton — você deseja que todas as partes do seu programa compartilhem a mesma configuração e estado do logger.
Você organizará seu código em dois arquivos:
logger.go: Implemente seu logger Singleton thread-safe.Crie uma struct
Loggercom dois campos:Prefix(string) para prefixos de mensagens de log, eMessageCount(int) para rastrear quantas mensagens foram registradas.Use variáveis de nível de pacote com
sync.Oncepara garantir que apenas uma instância seja criada. Implemente uma funçãoGetLogger()que inicializa o logger com um prefixo padrão de"[LOG]"e retorna a instância singleton.Adicione estes métodos ao seu Logger:
SetPrefix(prefix string)- altera o prefixo do loggerLog(message string) string- incrementa a contagem de mensagens e retorna uma string formatada:[prefix] #[count]: [message]GetCount() int- retorna o número total de mensagens registradas
main.go: Demonstre que múltiplas chamadas paraGetLogger()retornam a mesma instância.Leia um novo valor de prefixo, depois leia uma contagem de mensagens seguida por cada mensagem a ser registrada.
Obtenha a instância do logger, defina o prefixo personalizado, registre cada mensagem e imprima o resultado. Após registrar todas as mensagens, obtenha a instância do logger novamente (para provar que é a mesma) e imprima a contagem total de mensagens.
As seguintes entradas serão fornecidas:
- Linha 1: Prefixo personalizado a ser definido
- Linha 2: Número de mensagens
- Linhas seguintes: Cada mensagem para registrar
Por exemplo, dado:
[APP]
3
Server started
User connected
Request processedSua saída deve ser:
[APP] #1: Server started
[APP] #2: User connected
[APP] #3: Request processed
Total messages logged: 3E dado:
[DEBUG]
2
Initializing cache
Cache readySua saída deve ser:
[DEBUG] #1: Initializing cache
[DEBUG] #2: Cache ready
Total messages logged: 2E dado:
[ERROR]
1
Connection failedSua saída deve ser:
[ERROR] #1: Connection failed
Total messages logged: 1O ponto principal é que não importa quantas vezes você chame GetLogger(), você sempre obtém a mesma instância com seu estado compartilhado. A contagem de mensagens persiste porque existe apenas um Logger em existência!
Folha de consulta
O padrão Singleton garante que um tipo tenha apenas uma instância em todo o seu programa e fornece um ponto de acesso global a ela. Isso é útil para recursos compartilhados, como gerenciadores de configuração, conexões de banco de dados ou loggers.
Implemente o Singleton usando uma variável de nível de pacote combinada com sync.Once para uma inicialização segura para threads (thread-safe):
package config
import "sync"
type Config struct {
DatabaseURL string
MaxRetries int
}
var (
instance *Config
once sync.Once
)
func GetInstance() *Config {
once.Do(func() {
instance = &Config{
DatabaseURL: "localhost:5432",
MaxRetries: 3,
}
})
return instance
}O sync.Once garante que a função de inicialização seja executada exatamente uma vez, mesmo quando múltiplas goroutines chamam GetInstance() simultaneamente. Cada chamada subsequente retorna a mesma instância:
cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 and cfg2 point to the same instanceUse o Singleton com moderação, pois ele introduz um estado global que pode dificultar os testes e ocultar dependências. Considere a injeção de dependência como uma alternativa.
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Ler o prefixo personalizado
prefix, _ := reader.ReadString('\n')
prefix = strings.TrimSpace(prefix)
// Ler o número de mensagens
countStr, _ := reader.ReadString('\n')
countStr = strings.TrimSpace(countStr)
count, _ := strconv.Atoi(countStr)
// Ler cada mensagem
messages := make([]string, count)
for i := 0; i < count; i++ {
msg, _ := reader.ReadString('\n')
messages[i] = strings.TrimSpace(msg)
}
// TODO: Obter a instância do logger usando GetLogger()
// TODO: Definir o prefixo personalizado usando SetPrefix()
// TODO: Registrar cada mensagem e imprimir o resultado
// TODO: Obter a instância do logger novamente (para provar que é a mesma)
// e imprimir a contagem total de mensagens no formato: "Total messages logged: X"
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica13Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão Abstract FactoryPadrão ObserverPadrão Strategy2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool