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

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

challenge icon

Desafio

Fácil

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

    Use variáveis no nível do pacote com sync.Once para garantir que apenas uma instância seja criada. Implemente uma função GetLogger() 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 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 várias chamadas a GetLogger() 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 processed

Sua saída deve ser:

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

E considerando:

[DEBUG]
2
Initializing cache
Cache ready

Sua saída deve ser:

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

E considerando:

[ERROR]
1
Connection failed

Sua saída deve ser:

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

A 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"
}
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

Pratique por conta própria: Compilador de Go online