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 durante todo o 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 registradores.
Em Go, implementamos Singleton usando uma variável no nível do pacote combinada com sync.Once para garantir a inicialização segura para threads:
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 várias 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 Singleton com moderação. Embora seja 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 criar um singleton Logger que garante a existência de apenas uma instância de logger em toda a sua aplicação! Este é um caso de uso clássico do padrão Singleton. Você quer que todas as partes do seu programa compartilhem a mesma configuração e o mesmo estado do logger.
Você organizará seu código em dois arquivos:
logger.go: Implemente seu logger Singleton seguro para uso com threads.Crie uma struct
Loggercom dois campos:Prefix(string) para os prefixos das mensagens de log eMessageCount(int) para acompanhar quantas mensagens foram registradas.Use variáveis no nível do pacote com
sync.Oncepara garantir que apenas uma instância seja criada. Implemente uma funçãoGetLogger()que inicialize o logger com um prefixo padrão de"[LOG]"e retorne 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 várias chamadas aGetLogger()retornam a mesma instância.Leia um novo valor de prefixo e, em seguida, 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. Depois de registrar todas as mensagens, obtenha a instância do logger novamente (para provar que é a mesma) e imprima o número 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 a ser registrada
Por exemplo, considerando:
[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 considerando:
[DEBUG]
2
Initializing cache
Cache readySua saída deve ser:
[DEBUG] #1: Initializing cache
[DEBUG] #2: Cache ready
Total messages logged: 2E considerando:
[ERROR]
1
Connection failedSua saída deve ser:
[ERROR] #1: Connection failed
Total messages logged: 1A principal ideia é que, não importa quantas vezes você chame GetLogger(), sempre obterá a mesma instância com seu estado compartilhado. A contagem de mensagens persiste porque existe apenas um Logger!
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leia o prefixo personalizado
prefix, _ := reader.ReadString('\n')
prefix = strings.TrimSpace(prefix)
// Leia o número de mensagens
countStr, _ := reader.ReadString('\n')
countStr = strings.TrimSpace(countStr)
count, _ := strconv.Atoi(countStr)
// Leia cada mensagem
messages := make([]string, count)
for i := 0; i < count; i++ {
msg, _ := reader.ReadString('\n')
messages[i] = strings.TrimSpace(msg)
}
// TODO: Obtenha a instância do logger usando GetLogger()
// TODO: Defina o prefixo personalizado usando SetPrefix()
// TODO: Registre cada mensagem e imprima o resultado
// TODO: Obtenha a instância do logger novamente (para provar que é a mesma)
// and print the total message count in format: "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 ExternosWorkspace e Módulos do GoPacotes e ImportsNomes Exportados vs. Não ExportadosIntrodução à OOP em GoStructs como ClassesDefinindo Métodos em StructsReceptores de Ponteiro vs. de ValorInicialização de StructsFunções ConstrutorasRecapitulação — Calculadora Simples4Interfaces
Introdução às InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Asserção de TipoSwitch de TipoComposição de InterfacesInterfaces Stringer e ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs. Não ExportadosEncapsulamento no Nível de PacoteMétodos Getters e SettersOcultação de Informações em GoRecapitulação - Registros de Alunos10Genéricos (Go 1.18+)
Introdução aos genéricosParâmetros de tipoRestrições de tipoStructs genéricasAlternativa 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 Strategy2Tipos e Structs em Profundidade
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosTags de StructStructs AnônimasStructs AninhadasValores Zero e PadrõesRecapitulação - Livro de Contatos5Composição em vez de herança
Por que Go não tem herançaNoções básicas de incorporação de structsPromoção de métodosIncorporação de várias structsIncorporação vs agregaçãoOcultação de métodos incorporadosRecapitulação - Hierarquia de funcionários8Tratamento de Erros e OOP
A interface errorTipos de Erro PersonalizadosEncapsulamento de Erros (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - Analisador de Arquivos3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs. ReferênciaA Função new()Coleta de Lixo em GoRecapitulação - Construtor de Lista Ligada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfacesColeções PolimórficasInjeção de DependênciasRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e comunicaçãoCanais com e sem bufferInstrução selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de structs thread-safeRevisão - Worker PoolPratique por conta própria: Compilador de Go online