Singleton Design Pattern
Tags: designpatterns, go, softwaredevelopment, coding
Published: 2024-07-03 19:54:23

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.

```go
package logger

import (
    "fmt"
    "sync"
)

type Logger struct {}

var loggerInstance *Logger
```

### 2 - Implementando a função NewInstance

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

```go
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
}
```

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

```go
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)
}
```

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

```go
package main

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

func main() {
	log := logger.NewInstance()
	log.Info("This is an example of log")
}
```
Esse é o resultado quando executamos o programa:
```shell
Creating new logger
2024-07-03T19:34:57.609599Z - INFO: This is an example of log
```

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

```go
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")
	}
}

```
Nossos logs mudaram e agora podemos ver que bloqueamos a criação de uma nova instancia:
```shell
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
```

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