Neste artigo vamos recordar o padrão Singleton e suas formas de implementação. |
O padrão singleton é um dos padrões mais conhecidos em engenharia de software. Essencialmente, um singleton é uma classe que permite que apenas uma única instância de si seja criada e, geralmente, fornece acesso simples a essa instância.
Geralmente, os singletons não permitem especificar nenhum parâmetro ao criar a instância - caso contrário, um segundo pedido para uma instância, mas com um parâmetro diferente, pode nos causar problemas.
Neste artigo vamos tratamos apenas a situação em que nenhum parâmetro é necessário. Tipicamente, um requisito dos singletons é que eles sejam criados preguiçosamente (lazy) - isto é, que a instância não seja criada até que seja primeiro necessária.
Existem várias maneiras diferentes de implementar o padrão singleton com C# e neste artigo vou começar mostrando a forma mais comum usada até chegar na abordagem mais segura e robusta.
Todas as implementações que mostrarei compartilham quatro características comuns :
Isso impede que outras classes o instanciem (o que seria uma violação do padrão). Observe que também evita subclassificação - se um singleton puder ser subclassificado uma vez, ele poderá ser subclassificado duas vezes e, se cada uma dessas subclasses puderem criar uma instância, o padrão será violado. |
Todas a implementações usam uma instância de uma propriedade estática pública como o meio de acessar a instância. (Em todos os casos, a propriedade poderia ser facilmente convertida em um método, sem impacto na segurança ou no desempenho da thread.)
Isso posto vamos ao código. (adaptado integralmente de https://csharpindepth.com/articles/singleton)
Versão 1 : Implementação padrão
Esta implementação não é thread-safe.
Assim duas threads distintas poderiam ter avaliado o teste (instance==null) e encontrado que isso era verdade (true), ai ambas as threads criariam instâncias o que viola o padrão.
Versão 2 : Implementação thread-safe
Essa implementação é thread-safe.
A thread retira um bloqueio (locker) em um objeto compartilhado e, em seguida, verifica se a instância foi ou não criada antes de criar a instância.
Isso cuida do problema da barreira de memória (o bloqueio garante que todas as leituras ocorram logicamente após a aquisição do bloqueio, e o desbloqueio garante que todas as gravações ocorram logicamente antes da liberação do bloqueio) e garante que apenas uma thread vai criar uma instância.
Infelizmente,
o desempenho é prejudicado à medida que um bloqueio é obtido toda vez que a
instância é solicitada.
Versão 3 :
Implementação thread-safe com bloqueio duplo
Essa implementação tenta ser thread-safe sem a necessidade de tirar um bloqueio todas as vezes. Infelizmente, existem quatro desvantagens nesta abordagem:
Não funciona no Java. O modelo de memória Java não garante que o construtor seja concluído antes que a referência ao novo objeto seja atribuída à instância.
Sem nenhuma barreira de memória, também está violando a especificação ECMA CLI - Common Language Infrastructure.
É fácil de cometer erros. O padrão precisa ser exatamente igual ao anterior - quaisquer mudanças significativas provavelmente afetarão o desempenho ou a correção.
Versão 4 : Implementação thread-safe sem usar bloqueio
Temos uma implementação thread-safe sem usar bloqueios.
Os construtores estáticos na linguagem C# são especificados para serem executados somente quando uma instância da classe for criada ou um membro estático for referenciado, e, para serem executados apenas uma vez por AppDomain.
Dado que essa verificação do tipo que está sendo construído recentemente precisa ser executada, o que quer que aconteça, será mais rápido do que adicionar uma verificação extra, como nos exemplos anteriores.
Esta implementação não é lazy ou preguiçosa, visto que se você tiver membors estáticos diferentes de Instância, a primeira referência a esses membros envolverá a criação da instância.
Versão 5 : Implementação lazy ou preguiçosa
Aqui, a instanciação é acionada pela primeira referência ao membro estático da classe aninhada, que ocorre apenas na Instância. Isso significa que a implementação é totalmente preguiçosa, mas tem todos os benefícios de desempenho dos anteriores.
Observe que, embora as classes aninhadas tenham acesso aos membros privados da classe envolvente, o inverso não é verdadeiro, portanto, a necessidade de a instância ser interna aqui. Isso não levanta nenhum outro problema, já que a classe em si é privada. O código é um pouco mais complicado para tornar a instanciação preguiçosa, no entanto.
Versão 6 : Implementação usando o tipo Lazy<T> (.NET 4.0)
Se você estiver usando a versão .NET 4 (ou superior), você pode usar o tipo System.Lazy<T> para tornar a a implementação lazy ou preguiçosa realmente simples. Tudo o que você precisa fazer é passar um delegado para o construtor que chama o construtor Singleton - o que é feito mais facilmente com uma expressão lambda.
Este código é
simples e funciona bem. Ele também permite que você
verifique se a instância foi criada ou não com a
propriedade IsValueCreated,
se você precisar disso.
O código acima implicitamente usa
LazyThreadSafetyMode.ExecutionAndPublication como
o modo de segurança de thread para o
Lazy<Singleton>. Dependendo
de suas necessidades, você pode experimentar outros
modos.
Esta seria a implementação mais robusta e com bom desempenho.(talvez não o melhor).
Concluindo você deve considerar que em muitos casos você não vai precisar ter um lazy total - a menos que sua inicialização de classe faça alguma coisa particularmente demorada ou tenha algum efeito colateral em outro lugar, provavelmente é bom deixar de fora o construtor estático explícito mostrado acima.
Isso pode aumentar o desempenho, pois permite que o compilador JIT faça uma única verificação (por exemplo, no início de um método) para garantir que o tipo tenha sido inicializado e, em seguida, assuma a partir daí. Se sua instância singleton for referenciada dentro de um laço(loop) relativamente apertado, isso pode fazer uma diferença de desempenho significativa.
Assim você deve decidir se a instanciação totalmente preguiçosa é necessária ou não e documentar essa decisão apropriadamente dentro da classe.
Pegue o código completo aqui: CShp_Singleton1.zip
Louvai ao Deus dos deuses; porque a sua benignidade dura
para sempre.
Louvai ao Senhor dos senhores; porque a sua benignidade
dura para sempre."
Salmos 136:1-3
Veja os
Destaques e novidades do SUPER DVD Visual Basic
(sempre atualizado) : clique e confira !
Quer migrar para o VB .NET ?
Quer aprender C# ??
Quer aprender os conceitos da Programação Orientada a objetos ? Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ? |
Referências:
Super DVD Vídeo Aulas - Vídeo Aula sobre VB .NET, ASP .NET e C#
Encapsulation and Inheritance in Object-Oriented Programming Languages
O padrão Strategy : utilização - Macoratti
NET - Usando padrões de projeto e princípios OOP na ... - Macoratti
C# - Boas Práticas : Aprendendo com maus exemplos ... - Macoratti
NET - O padrão de projeto Mediator - Macoratti
NET - Design Patterns - Uma abordagem Prática - Macoratti