Menu
Coddy logo textTech

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ância

Use 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.

challenge icon

Desafio

Fácil

Vamos 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 Logger com dois campos: Prefix (string) para prefixos de mensagens de log, e MessageCount (int) para rastrear quantas mensagens foram registradas.

    Use variáveis de nível de pacote com sync.Once para garantir que apenas uma instância seja criada. Implemente uma função GetLogger() 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 logger
    • Log(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 para GetLogger() 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 processed

Sua saída deve ser:

[APP] #1: Server started
[APP] #2: User connected
[APP] #3: Request processed
Total messages logged: 3

E dado:

[DEBUG]
2
Initializing cache
Cache ready

Sua saída deve ser:

[DEBUG] #1: Initializing cache
[DEBUG] #2: Cache ready
Total messages logged: 2

E dado:

[ERROR]
1
Connection failed

Sua saída deve ser:

[ERROR] #1: Connection failed
Total messages logged: 1

O 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 instance

Use 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"
}
quiz iconTeste seus conhecimentos

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