De que se trata o artigo

O artigo tratará do padrão de projeto Singleton, mostrando como implementá-lo e que erros comuns devem ser evitados. Também serão abordadas certas peculiaridades provindas dos novos recursos da Delphi Language que podem ajudar na construção de Singletons.

Em que situação o tema é útil

Singleton é uma classe que deve ter apenas uma instância. É muito mais usado para representar classes de sistema e de serviços gerais do que classe de negócio/domínio. Em uma arquitetura multicamadas e/ou em DDD eles são usados para representar conexões ao banco de dados, servers, serviços como envio de e-mail, SMS entre outras funções. Esse tema é útil principalmente para as empresas que vendem frameworks para que outras possam desenvolver sistemas mais rapidamente. Se uma classe deve ser um singleton os programadores do framework devem assegurar isso.

Singleton

Um padrão de projeto muito comum e até mesmo visto por alguns como um “anti-padrão” é o singleton. Singletons são usados para representar serviços de sistema, e não serviços de negócio. São apropriados para instâncias ou seções de banco de dados, mecanismos de persistência, envios de e-mail, fila de impressão e assim por diante. Muitas vezes os singletons são implementados de maneira errada, até mesmo perigosa. Não existe maneira “nativa” de se criar um singleton em Delphi, mas existem alternativas elegantes.

Singleton é um padrão de projeto bastante conhecido da GOF (Gang of Four) onde uma classe pode ter apenas uma instância. Ele tenta garantir que a classe seja instanciada uma única vez e fornece um ponto de acesso global a ela.

Alguns componentes geralmente modelados como singletons são: camadas de acesso a dados, serviços de comunicação, como envio de e-mails, o objeto de configuração do sistema como um todo e toda e qualquer informação que tenha uma fonte única e que deva ser compartilhada por todas as outras classes.

Especialmente se uma classe de serviço ou de sistema é muito grande e custosa para se instanciar o uso de singleton faz com que durante toda a execução do sistema a classe seja instanciada uma única vez.

Classes que usam recursos escassos e / ou que não podem ser compartilhados, por exemplo, no caso de leitura e escrita em um arquivo de texto, apenas uma instância da classe teria acesso para leitura e escrita por vez. Outras tentativas de abrir um mesmo arquivo para leitura e escrita resultariam em um deadlock.

O uso de singletons deve ser justificado, visto que recentemente muitos têm considerado o singleton um anti-pattern. Afinal muitos dos problemas que alguns programadores costumam resolver usando singletons são possivelmente solucionáveis com injeção de dependência.

Singletons não devem ser usados como variáveis globais, mas sim como objetos que mantêm um estado comum e público a todas as classes do sistema. É o tipo de padrão de projeto que, na dúvida, é melhor não usar. Ele não deve ser usado como uma “variável global orientada a objeto”, nunca.

Nota: Se o singleton possui apenas métodos, mas não atributos, muitas pessoas implementam o singleton como uma classe com vários métodos de classe. Isso não é recomendável, pois viola vários princípios da orientação a objeto (regras de negócio espalhadas que não fazem parte de nenhuma classe). E mesmo que a classe não precise ser instanciada, ela ainda pode ser, embora neste caso não faça muita diferença. Você pode ter várias classes dependendo do seu singleton, mas é imprescindível que o singleton não dependa de nenhuma outra classe, pois isso causaria uma bagunça muito difícil de se consertar.

É realmente muito raro singletons serem necessários, mas tenho visto muitos membros da comunidade implementar singletons onde ele não é estritamente necessário, e o pior, implementar de maneira errada. Em alguns casos a implementação errada não causa nenhum impacto no sistema, mas em muitos casos elas podem causar erros de Access violation e memory leaks.

Para explicar o porquê disso, deve-se raciocinar: se os singletons não devem ser criados, eles também não devem ser destruídos, pois uma vez destruídos quem os recriará? E pior: as referências a ele (variáveis que o contém/apontam para ele) passam a ser inválidas no momento em que ele é destruído. Inválidas, porém diferentes de nil. Criar um singleton que já tem uma instância também é problemático. Depois que a segunda instância é criada e atribuída ao objeto global a referência ao objeto anterior é perdida. Sem referência, como o objeto será destruído? Causa-se assim um memory leak.

Um singleton também não deveria ser atribuível a outras variáveis ou passado como parâmetro para um método, pois não dá para garantir muito o que se fará de errado com ele.

Na seção de links desse artigo encontra-se uma série de URLs de blogs e fóruns discutindo sobre o uso de singletons e porque eles são considerados um anti-pattern.

Note que o que está sendo questionado é o uso indiscriminado do singleton como variável global. Para programadores que estão saindo de ambientes procedurais ou orientados a eventos e tendo suas primeiras incursões na programação OO pura os singletons podem parecer uma atraente alternativa às variáveis públicas globais.

Os leitores deste artigo poderão se perguntar: “mas para que fazer um artigo sobre esse assunto se não há consenso com relação aos seus benefícios?”. A resposta é simples, independente do uso correto do singleton saber como criá-lo é um conhecimento interessante para habitar a “caixa de ferramentas” de qualquer programador, mas o mais interessante é que o modo de se criar tal padrão pode mostrar detalhes interessantes do próprio Delphi.

Maneiras de implementar

A maneira mais comum de se implementar um singleton é por ter uma variável pública na seção implementation da unit onde a classe reside a fim de guardar uma instância da classe a ser criada, e colocar na seção interface uma função estática pública global que retorne esta instância caso criada. Se a variável for nil então a função primeiro a cria e depois a retorna.

Uma melhoria nesse tipo de implementação seria transformar a variável pública global em uma class var (campo estático de classe) que seria limitada unicamente ao escopo da classe, e uma class function (método de classe) para substituir a função estática pública global.

As desvantagens claras dessas duas abordagens é que elas não estão protegidas caso um programador desavisado que não tenha lido o “manual” resolva usar o método create, ou pior, o Destroy.

A Listagem 1 mostra uma comparação entre essas duas formas. A seção de implementação foi omitida porque além desse exemplo ser teórico, é bastante trivial. No código-fonte que acompanha esta edição podem ser analisadas implementações completas de singletons.

...
Quer ler esse conteúdo completo? Tenha acesso completo