Singleton Design Pattern

rflpazini

Rafael Pazini

Posted on July 3, 2024

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.

💖 💪 🙅 🚩
rflpazini
Rafael Pazini

Posted on July 3, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Singleton Design Pattern
designpatterns Singleton Design Pattern

July 3, 2024