DEV Community

Rafael Pazini
Rafael Pazini

Posted on

Singleton Design Pattern

O padrão de design Singleton é um dos mais importantes e frequentemente utilizados na programação de software. Ele assegura que uma classe tenha apenas uma única instância durante o tempo de execução da aplicação e fornece um ponto de acesso global a essa instância. Neste artigo, discutiremos a importância do Singleton, como implementá-lo em Golang e os benefícios que ele traz, especialmente em ambientes concorrentes.

O que é o Singleton?

O Singleton é um padrão de design que restringe a instância de uma classe a uma única instância. É particularmente útil em situações onde um único ponto de controle ou um único recurso compartilhado é necessário, como:

  • Gerenciadores de configuração, onde as configurações da aplicação precisam ser centralizadas.
  • Pools de conexão a banco de dados, onde um número limitado de conexões deve ser gerenciado de forma eficiente.
  • Loggers, onde a consistência dos registros é crucial.

Por que utilizar o Singleton?

Vou listar alguns pontos sobre a implementação desde Pattern que fazem mais sentido e também para mostrar que nem tudo são flores, alguns dos problemas que podemos ter com ele.

Vantagens

  • Consistência Global: Garante que todos os pontos da aplicação utilizem a mesma instância, proporcionando consistência de dados e comportamento.
  • Controle de Acesso: Centraliza o controle de criação e acesso à instância, facilitando a manutenção e o gerenciamento do ciclo de vida do objeto.
  • Eficiência de Recursos: Evita a criação desnecessária de múltiplas instâncias, economizando recursos de memória e processamento.

Desvantagens

  • Dificuldade de Testes: Singletons podem tornar a escrita de testes unitários mais difícil, pois introduzem estados globais que precisam ser gerenciados.
  • Aumento do Acoplamento: O uso excessivo de Singletons pode levar a um acoplamento mais rígido entre componentes, dificultando a manutenção e evolução da aplicação.

Implementando uma Singleton

Para implementar uma singleton vou utilizar Golang. Nesta linguagem temos que ter uma atenção especial à concorrência para garantir que apenas uma instância seja criada, mesmo quando múltiplas goroutines tentam acessar a instância simultaneamente.

Para deixar nosso exemplo mais próximo do mundo real, vamos criar um Logger para nossa aplicação. Um logger é uma ferramenta comum em aplicações que precisa ser única para garantir a consistência dos logs.

1 - Definindo a estrutura

Primeiro, definimos a estrutura que queremos que tenha uma única instância.

package logger

import (
    "fmt"
    "sync"
)

type Logger struct {}

var loggerInstance *Logger
Enter fullscreen mode Exit fullscreen mode

2 - Implementando a função NewInstance

A função NewInstance é responsável por retornar a instância única da estrutura Singleton. Utilizamos um mutex para garantir a segurança em ambientes concorrentes, implementando a verificação dupla de bloqueio (double-checked locking) para eficiência.

package logger

import (
    "fmt"
    "sync"
)

type Logger struct{}

var logger *Logger
var mtx = &sync.Mutex{}

func NewInstance() *Logger {
    if logger == nil {
        mtx.Lock()
        defer mtx.Unlock()
        if logger == nil {
            fmt.Println("Creating new Logger")
            logger = &Logger{}
        }
    } else {
        fmt.Println("Logger already created")
    }
    return logger
}
Enter fullscreen mode Exit fullscreen mode

3 - Implementando os tipos de log

Uma ferramenta de Log sempre tem alguns tipos de log, como por exemplo Info para apenas mostrar as informações, Error para mostrar erros e assim por diante. É uma forma de filtrarmos também o tipo de informação que queremos mostrar em nossa aplicação.

Então vamos criar um método que irá mostrar nosso log com o tipo Info. Para isso vamos criar uma função que receberá nossa mensagem de log e a formatará para o formato INFO.

package logger

import (
    "fmt"
    "sync"
    "time"
)

const (
    INFO    string = "INFO"
)

type Logger struct{}

var logger *Logger
var mtx = &sync.Mutex{}

func NewInstance() *Logger {
    if logger == nil {
        mtx.Lock()
        defer mtx.Unlock()
        if logger == nil {
            fmt.Println("Creating new logger")
            logger = &Logger{}
        }
    } else {
        fmt.Println("Logger already created")
    }
    return logger
}

func (l *Logger) Info(message string) {
    fmt.Printf("%s - %s: %s\n", time.Now().UTC().Format(time.RFC3339Nano), INFO, message)
}
Enter fullscreen mode Exit fullscreen mode

4 - Usando o Logger

E para utilizar nosso novo logger, vamos instancia-lo dentro do nosso package main e criar um log para ver como funciona essa implementação.

package main

import (
    "playground-go/pkg/logger"
)

func main() {
    log := logger.NewInstance()
    log.Info("This is an example of log")
}
Enter fullscreen mode Exit fullscreen mode

Esse é o resultado quando executamos o programa:

Creating new logger
2024-07-03T19:34:57.609599Z - INFO: This is an example of log
Enter fullscreen mode Exit fullscreen mode

Se quisermos testar se o NewInstance está realmente garantindo que apenas teremos uma instancia rodando, podemos fazer o seguinte teste.

package main

import (
    "fmt"
    "playground-go/pkg/logger"
)

func main() {
    log := logger.NewInstance()
    log.Info("This is an example of log")

    log2 := logger.NewInstance()
    log2.Info("This is another example of log")

    if log == log2 {
        fmt.Println("same instance")
    } else {
        fmt.Println("different instance")
    }
}

Enter fullscreen mode Exit fullscreen mode

Nossos logs mudaram e agora podemos ver que bloqueamos a criação de uma nova instancia:

Creating new logger
2024-07-03T19:45:19.603783Z - INFO: This is an example of log
Logger already created
2024-07-03T19:45:19.603793Z - INFO: This is another example of log
same instance
Enter fullscreen mode Exit fullscreen mode

Conclusão

O padrão Singleton é uma ferramenta poderosa para garantir que apenas uma instância de uma classe específica exista durante o tempo de execução da aplicação. No exemplo do logger, vimos como esse padrão pode ser aplicado para garantir a consistência dos logs em toda a aplicação.

Espero que isso ajude você a entender melhor o Singleton em Golang.

Top comments (0)