Continuando com a série Design Patterns vamos falar sobre Singleton.
Antes de começar a despejar um monte de informação, vamos criar um cenário...
Você é um programador solo, que cria o projeto, trabalha sozinho em todos os passos do mesmo? Ou você trabalha com uma equipe com vários programadores e cada um cria uma parte do projeto visando um todo para chegar a um único resultado? Em ambos os caso o singleton será útil, no primeiro vai resolver os casos dos programadores que esquecem até mesmo os próprios conceitos utilizados na implementação de uma classe, e no segundo vai garantir que ninguém vai criar mais de um objeto da mesma classe, vamos ver por quê?
Para alegria geral na nação, o padrão singleton é o mais simples em termos de seu diagrama de classe, na verdade o diagrama só contém uma classe.
Digamos que temos uma classe qualquer que chamaremos de Singleton e queremos que em toda a aplicação só exista uma única instância dessa mesma classe, e que você tenha que garantir que isso aconteça, no caso de ser um programador solo, de que você não se esqueça disso e no caso de uma equipe, que ninguém viole essa regra, bem com o padrão de projeto Singleton garantiremos uma única instância para a classe em questão.
Vamos analisar uma criação de uma classe singleton clássica não thread safe.
public class Singleton
{
private static Singleton _unicaInstancia;
public int _valor { get; set; }
// Outras variáveis e propriedade aqui
// Contrutor privado
private Singleton() { }
public static Singleton getInstance()
{
if (_unicaInstancia == null)
{
_unicaInstancia = new Singleton();
}
return _unicaInstancia;
}
public void ExibirValor()
{
Console.WriteLine(_valor);
}
// Outros metodos aqui
}
Observe que o construtor é privado não permitindo a criação da classe de fora dela, e que temos um método getInstance que tem é estática e é esse mesmo método que verifica se o objeto _unicaInstancia que também deve ser estática já foi instanciado ou se é necessario criar a instancia e retorna o mesmo para a invocação do método, vamos ver um exemplo utilizando essa classe.
using System;
class SingletonClient
{
static void Main(string[] args)
{
Singleton singCliente1 = Singleton.getInstance();
singCliente1._valor = 10;
Singleton singCliente2 = Singleton.getInstance();
singCliente2.ExibirValor();
singCliente2._valor = 20;
singCliente1.ExibirValor();
Console.ReadKey();
}
}
Vamos observar as linhas das criações dos objetos singCliente1 e singCliente2, veja que não utilizamos o new para criações das classes e sim solicitamos ao método getInstance da classe a única instancia dela. Setamos um valor para a propriedade _valor do singCliente1 e somente depois criamos o objeto singCliente2 e invocamos o método ExibirValor do objeto singCliente2, em seguida setamos um valor para a propriedade _valor do singCliente2 e invocamos o método ExibirValor do objeto singCliente1.
O resultado na tela do console vai ser:
Por que isso ocorreu se setamos o valor 10 no objeto singCliente1 e não setamos nada no singCliente2 e ele exibiu o valor do objeto singCliente1?
Bem, ocorre que ambos os objetos é uma referência o objeto estático que está dentro da classe, esse objeto é o único objeto instanciado em todo o projeto, o que foi feito, foi criar um ponto global de acesso a esse objeto.
Como foi dito a solução acima não vai ter o resultado esperado quando utilizado em MultiThreading, mais não se preocupe, vamos ver uma outra solução bem parecida com essa, na verdade algumas alterações feitas somente e já teremos uma classe singletam thread safe que poderá ser usada em varias threads.
using System;
using System.Threading;
public class Singleton
{
// Responsável pela criação do objeto singleton
class CriaInstancia
{
internal static readonly Singleton instancia = new Singleton();
}
public int _valor { get; set; }
private Singleton() { }
public static Singleton getInstance()
{
return CriaInstancia.instancia;
}
public void ExibirValor()
{
Console.WriteLine(_valor);
}
}
class Program
{
static void Main(string[] args)
{
Singleton singCliente1 = Singleton.getInstance();
singCliente1._valor = 10;
//Nova thread
Thread teste = new Thread(TesteThread);
teste.Start();
teste.Join();
//Final thread
Singleton singCliente2 = Singleton.getInstance();
singCliente2.ExibirValor();
singCliente2._valor = 20;
singCliente1.ExibirValor();
Console.ReadKey();
}
private static void TesteThread()
{
Console.WriteLine("Inicio Thread");
Singleton singCliente3 = Singleton.getInstance();
singCliente3.ExibirValor();
singCliente3._valor = 100;
Console.WriteLine("Fim Thread");
}
}
Foi adicionado na classe Singleton a classe CriaInstancia que agora contém a única instancia da classe Singleton com os modificadores internal, static e readonly permitindo assim que em qualquer thread seja utilizado o mesmo objeto.
/ Responsável pela criação do objeto singleton
class CriaInstancia
{
internal static readonly Singleton instancia = new Singleton();
}
A classe Singleton não tem mais o objeto statico da sua classe que servia para instanciar o objeto caso fosse a primeira solicitação. Com estas simples alterações garantiremos que em todas as threds criadas no sistema vamos ter o resultado esperado, uma única instância do objeto.
Caso você queira um construtor para o CriaInstancia você deverá cria-lo com o modificador static ficando desta forma:
class CriaInstancia
{
internal static readonly Singleton instancia = new Singleton();
static CriaInstancia()
{
instancia._valor = 200;
}
}
Finalizamos mais este artigo, espero ter alcançado meus objetivos que erá passar um pouco do conceito do design pattern singleton de forma clara e com um facil entendimento.
Estarei falando sobre outros padrões nos proximos artigos.
Fontes:
FREEMAN, ERIC & FREEMAN, ELISABETH – Use a Cabeça! Padrões de Projetos (Design Patterns), 2ª Edição