[Delphi] Design Patterns GoF – Singleton

Boa noite, pessoal, tudo certo?
Hoje é dia de finalizar a série de artigos sobre os padrões de projeto criacionais. A partir do próximo artigo, abordaremos os padrões da categoria “Estrutural”.
O último Design Pattern dessa natureza é o Singleton, do qual tenho certeza que já ouviram falar! Acompanhe o artigo para conhecer o propósito, a aplicabilidade e – o mais importante – os cuidados ao utilizar este padrão.

Introdução

Já caíram em algumas situações em que era necessário instanciar e destruir objetos da mesma classe várias vezes? Por exemplo, imagine um classe que realiza cálculos para o Departamento Pessoal, como FGTS, INSS, descontos e créditos. Em cada uma dessas operações, é necessário instanciar um objeto da classe de cálculos, invocar a função desejada e então liberá-la da memória. Se os cálculos forem constantes, este procedimento torna-se redundante.

Bom, poderíamos ter um objeto global para ser utilizado em todos os métodos, certo?
Concordo. É uma solução. Porém, vou um pouco mais além: suponha, então, que essa classe de cálculos também é utilizada com frequência nos módulos fiscais, financeiros e contábeis. Em suma, precisamos utilizá-la em telas distintas. Um objeto global não funcionaria, ao menos que fosse um objeto global da aplicação, e não de uma tela específica.

Opa, cheguei aonde queria! Este objeto global da aplicação pode ser definido como um Singleton!

Singleton

O padrão de projeto Singleton tem o propósito de fornecer um ponto único de acesso à instância de um objeto, de modo que qualquer local da aplicação consiga acessá-lo.

Uma necessidade conhecida do Singleton é a leitura do perfil de usuários. Geralmente, em sistemas multiusuários, é comum buscar as permissões de acesso e outros dados do usuário (como customizações, padrões, temas, etc…) para cada tela que for acessada. Pensando do modo tradicional, cria-se um objeto da classe, faz a leitura dos dados solicitados, e o destrói em seguida.

ara evitar essa redundância de criação e liberação de objetos, podemos utilizar um Singleton. O objeto da classe será criada apenas uma vez (como na inicialização da aplicação) e essa mesma instância será utilizada por todos os locais que a solicitarem. É como disse anteriormente: o Singleton assemelha-se com um objeto global de toda a aplicação.

Uma das vantagens é a redução de processamento ao criar e liberar objetos constantemente, contribuindo para o desempenho do sistema. Outra vantagem é que, com o Singleton, pode-se compartilhar dados entre telas, já que representa um único objeto. Por exemplo, se alterarmos a propriedade X do Singleton no Cadastro de Clientes, este mesmo valor será lido ao acessarmos a tela de Contas a Pagar. Podemos compará-lo a um repositório de dados compartilhados.

Antes de prosseguir, gostaria de pedir que vocês acompanhassem o artigo até o fim. Após a parte prática, achei necessário – ou, talvez, indispensável – manifestar algumas ressalvas e opiniões particulares sobre o Singleton. Do ponto de vista técnico, este padrão de projeto apresenta algumas restrições.

Exemplo de codificação do Singleton

Para o nosso exemplo prático, achei interessante a ideia de uma funcionalidade que registra logs em uma aplicação que realiza sorteios (como manipulação do cadastro de participantes, pessoas sorteadas, etc). Criaremos, então, uma classe que registra logs de alguns eventos da aplicação, denominada Logger. Independente da tela em que o usuário estiver, acessaremos o mesmo objeto do Logger para consumir os métodos. Será um endereço único na memória.

Primeiramente, para fins de compreensão, optei por disponibilizar a classe completa do Logger, que será o nosso Singleton:

Observe que criamos uma variável da classe chamada Instancia, que será responsável por armazenar o ponto único de acesso ao objeto.

A mecânica do Singleton acontece no método Create (que é chamado pelo método ObterInstancia): se a variável de classe (Instancia) já existir na memória, será retornada ao chamador. Caso contrário, o objeto é criado antes de ser retornado. Na prática, o objeto será criado apenas na primeira vez em que é solicitado. Nas chamadas subsequentes, o objeto já estará instanciado. O objetivo principal é manter apenas uma instância na memória.

É importante também ressaltar que o método ObterInstancia é declarado como class function. Isso permite que podemos chamá-lo sem a necessidade de instanciar o objeto.

O que é o método NewInstance?

Pois bem, suponha que o desenvolvedor esqueça do método ObterInstancia e acidentalmente invoca o método Create da classe do Logger:

Se isso ocorrer, o Singleton perde o sentido. Teremos duas (ou mais) instâncias da mesma classe, e não um objeto único. Para prevenir esse equívoco, sobrescrevemos a função NewInstance, que é indiretamente chamada pelo Create no ancestral TObject, para aplicar as nossas condições. Dessa forma, mesmo que o desenvolvedor utilize o método ObterInstancia ou Create, a mesma instância será retornada. Boa, hein? 🙂

Agora é simples: cada vez que precisarmos registrar um log, basta utilizarmos a codificação abaixo:

Perfeito!

Ops, esqueci de uma pequena observação: no código em que ocorre todo o mecanismo, temos uma condição IF para verificar se o objeto já existe. Isso implica que, toda vez que chamarmos o método ObterInstancia, essa condição será processada. Oras, se o objeto é criado somente na primeira chamada, essa condição torna-se inútil em todas as chamadas posteriores, concordam?

Uma alternativa para solucionar essa pendência é criar o objeto na inicialização da aplicação, ao invés de criá-lo quando o Singleton for requisitado na primeira vez. O método NewInstance, por sua vez, seria ajustado para apenas retornar a instância, já que haveria uma garantia de que ela já foi criada:

Deseja ver o Singleton funcionando em um projeto? Baixe o exemplo no link abaixo (com várias melhorias), execute o projeto, faça algumas operações e, por fim, clique no botão Abrir Log. Todo o conteúdo do arquivo será gerado pelo Singleton.

Cuidados e ressalvas

Leitores, agora é hora de bater um papo sério…

Tudo em excesso é ruim. O Singleton, quando utilizado abusivamente, torna-se um Anti-Pattern (ou Anti-Padrão). Este termo é aplicado a práticas e/ou soluções que, quando empregados incorretamente, são contra-produtivos.

O propósito do Singleton, de criar objetos globais, tornou-se relativamente comum na programação, principalmente por facilitar e agilizar as atividades de codificação. Na verdade, muitos fazem uso dessa prática mas não sabem que é um Singleton. Outros também adquirem o hábito de criar classes “utilitárias”, que possuem funções úteis e regras de negócio para serem compartilhadas em qualquer local do sistema.

Na minha opinião, isso é arriscado. Ao invés de reduzir o processamento, é possível que o desempenho do sistema seja prejudicado em função de um objeto que ficará residente na memória o tempo todo. O problema agrava-se ainda mais quando há vários Singletons no projeto e todos são criados (e mantidos) de uma vez só. Acredite: isso não é uma boa prática. Embora seja um padrão de projeto, o Singleton deve ser empregado em situações especiais, e não como um mero agregador de funções úteis ou agente de reaproveitamento de código.

O Logger apresentado no artigo é um exemplo de situação especial, pois representa um objeto que será utilizado com muita frequência. Se o uso for eventual ou intervalado, esqueça o Singleton. Crie os objetos sob demanda. O código fica mais profissional e o consumo de memória da aplicação será menor.

Eu, particularmente, penso três vezes antes de utilizá-lo. Recomendo que todos façam o mesmo.

 

Próximo artigo: início dos padrões estruturais.
Aguardo vocês lá! 🙂


André Celestino