C# -  Padrão Singleton (revisitado)


Neste artigo vou apresentar com o padrão de criação Gof Singleton.

O padrão Singleton é um padrão de criação que permite garantir que uma classe tenha apenas uma instância fornecendo um ponto de acesso global a essa instância.

Nesta definição destacamos dois pontos no padrão Singleton :

1-   Garantir que uma classe tenha somente uma instância.

Com isso nós podemos controlar o acesso a algum recurso compartilhado como por exemplo um banco de dados
um arquivo, etc.

2- Fornecer um ponto de acesso global a essa instância.

Isso permite que você acesse um objeto de qualquer lugar do programa mas protege a instância de ser sobrescrita
por outro código.

Essa é a intenção principal do padrão Singleton.

Diagrama UML

No diagrama UML deste padrão temos a classe Singleton que que declara um membro estático chamado instance do tipo Singleton usado para tratar a referência a única instância da classe. Note que ele é estático e privado.

A seguir temos um construtor privado sem parâmetros chamado Singleton de forma que ninguém consiga
instanciar essa classe somente a própria classe Singleton.

Depois  temos também um membro privado e estático do mesmo tipo da classe chamado Instance. Esse membro  que pode ser uma propriedade ou um método dependendo da implementação.  vai verificar se a instância já foi iniciada.

Caso não tenha sido iniciada ele vai fazer a criação da instancia pela primeira e única vez.

Então esses são os elementos do diagrama o UML do padrão Singleton.

Exemplos de uso do padrão Singleton

Como exemplo de usos deste padrão podemos destacar:

1- Podemos usar o padrão Singleton para controlar a concorrência de acesso a recursos compartilhados como por exemplo o acesso aos recursos de um banco de dados ou a arquivos;

2- Quando você tem uma classe que é usada por várias partes do sistema e ela não gerencia nenhum estado como arquivos de log onde estamos apenas realizando operações de escrita em arquivo;

3- No compartilhamento de dados quando você deseja manter valores de configuração comuns em um Singleton para que possam ser acessados por outras partes da aplicação  como por exemplo se você tiver quaisquer valores constantes ou valores de configuração comum  poderá mantê-los em um Singleton para que eles possam ser lidos por outras partes da sua aplicação.


Quando podemos usar o padrão Singleton

Podemos fazer uma verificação respondendo algumas perguntas para ter uma ideia se podemos considerar ou não usar o padrão:

1- Faz sentido a classe ter apenas uma instância ?

Se você responder SIM então o pode considerar usar padrão Singleton.
.
2- Voce vai usar o padrão Singleton para substituir variável global ?

Se você responder SIM então o padrão Singleton não deve ser considerado  porque variáveis globais não são uma boa prática e devem ser evitadas.  Você não deve usar o Singleton para substituir variáveis globais ou para funcionar como um acesso a variáveis globais.

3- A classe poderá ter mais de uma instância ?
 
Se você responder SIM então não tem sentido você usar o padrão porque a classe vai ter mais de uma instância e usamos o Singleton justamente para obter apenas ter uma única instância.

Aplicando o padrão Singleton

Veremos agora uma implementação básica do padrão Singleton em um projeto do tipo Console usando o .NET 5.0.

Vamos criar a classe Singleton com o código abaixo:

public class Singleton
{
    private static Singleton instance;

    private Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
           if (instance == null)
           {
              instance = new Singleton();
           }
           return instance;
       }
   }
}

Aqui temos a classe Singleton e nesta classe temos:

1- Um campo privado e estático do tipo Singleton  que garante o ponto de acesso global  à instancia da classe

2- A seguir temos um construtor privado sem parâmetros e assim somente a classe Singleton poderá acessar o construtor para poder criar a sua instância

3- Depois definimos um membro estático chamado Instance do tipo da classe que retorna a instancia da sua própria classe.  Aqui usamos uma propriedade, e, ela verifica se a variável instance já foi iniciada e caso não tenha sido criamos a instância de Singleton (isso é feito na primeira e unica vez).

Esta seria a implementação padrão mais básica do Singleton.

Note que o construtor da classe Singleton esta oculto do código cliente, e a propriedade Instance é o único modo de obter o objeto Singleton.

Para verificar a implementação do padrão vamos incluir o código em destaque na classe Singleton:

using static System.Console;

public class Singleton
{
    private static Singleton instance;

    private int numeroDeInstancias = 0;
    private Singleton()
    {
        WriteLine("Instanciando dentro do construtor privado");
        numeroDeInstancias++;
        WriteLine($"Número de Instâncias = {numeroDeInstancias}\n");

    }
    public static Singleton Instance
    {
        get
        {
           if (instance == null)
           {
               WriteLine(“Criando a primeira instância");
               instance = new Singleton();
           }
          return instance;
       }
   }
}

Agora na classe Program inclua no método Main o código a seguir:

static void Main(string[] args)
{
      Console.WriteLine("##### Padrão Singleton #####\n");
      Console.WriteLine("Tentando criar uma instância s1.");

       Singleton s1 = Singleton.Instance;

       Console.WriteLine("Tentando criar uma instância s2.\n");

       Singleton s2 = Singleton.Instance;

  if (s1 == s2)
  {
       Console.WriteLine("Existem somente uma instância (s1==s2)\n");
      }
 else
 {
     Console.WriteLine("Existem instâncias diferentes (s1 e s2)\n");
 }
 Console.Read();
}

Neste código estamos tentando criar duas instâncias da classe Singleton e a seguir comparando as duas instâncias s1 e s2.

Se elas forem iguais isso indica que na verdade temos apenas uma única instância criada caso contrário teremos duas instâncias distintas.

Executando o projeto teremos o resultado abaixo:



Vemos assim que na implementação somente podemos criar uma instância da classe Singleton.

No entanto existe um problema com esta implementação : Ela não é segura para Threads.
 

Assim esta implementação não vai funcionar em um ambiente Multihread.  Veremos porque isso ocorre.

Isso ocorre pelo seguinte :   Havendo mais de uma thread a avaliação da existência da instância pode ocorrer  simultaneamente  e assim poderão ser criadas duas instancias diferentes violando o padrão.

Assim essa implementação padrão não deve ser usada e na outra parte do artigo iremos mostrar uma implementação mais robusta do padrão Singleton que pode ser usada em um ambiente multithread.

Pegue o código do projeto aqui :
Singleton1.zip

"Mas o fruto do Espírito é: amor, gozo, paz, longanimidade, benignidade, bondade, fé, mansidão, temperança.
Contra estas coisas não há lei."
Gálatas 5:22,23

Referências:


José Carlos Macoratti