Sempre que possível, irei tratar aqui, neste blog, de assuntos relacionados à melhor organização e reutilização de código, das boas práticas de programação, da Programação Orientada a Objetos e assim por diante. Sendo assim, a partir do momento que passamos a prestar mais atenção nos padrões de projeto (Design Patterns), fica fácil perceber que eles nos ajudam e muito nesta direção.
Neste post, apresentarei o padrão Singleton.
Cada design pattern tem um propósito, um objetivo. No caso do Singleton, o padrão garante a existência de apenas uma instância de uma classe, mantendo um ponto global de acesso ao seu objeto. Para facilitar o entendimento, vamos a um exemplo prático.
Construindo o exemplo
Em nossos projetos, muitas vezes temos algumas tarefas repetitivas, como por exemplo, o acesso a dados de um usuário logado ou o acesso a um banco de dados. Ok, eu sei que não precisamos ficar nos conectando ao banco a todo momento. Para isso, é mais comum a utilização de um componente de acesso ao banco num Datamodule e a conexão à base de dados se fazendo necessário somente na abertura do programa. Mas por ser mais simples, irei utilizar esta ideia, ou seja, a de obter a conexão de um banco de dados via código.
Para o exemplo, irei utilizar o bom e velho IBX (mas poderia ser qualquer outro conjunto de componentes de acesso, como o dbExpress e ADO).
No Delphi (estou utilizando agora o XE2), crie uma nova aplicação. No formulário em branco coloque três botões, dois edits e um memo, deixando o formulário parecido com este:
Crie uma nova unit com o nome de uConexao. Salve.
Nesta nova unit, vamos criar uma classe chamada TConexao:
[sourcecode language=”delphi”]
unit uConexao;
interface
uses IBDatabase, System.Classes, System.SysUtils;
type
TConexao = class(TIBDatabase)
private
class var FInstancia: TConexao;
class function getInstancia: TConexao; static;
public
class property Banco: TConexao read getInstancia;
end;
implementation
{ TConexao }
uses forms;
class function TConexao.getInstancia: TConexao;
begin
If not Assigned(FInstancia) Then
FInstancia := TConexao.Create(Application);
Result := FInstancia;
end;
[/sourcecode]
No código acima, criamos uma classe chamada TConexao derivada de TIBDatabase. Ela tem uma proriedade (Banco) que, através do método getInstancia, permitirá a criação de apenas um objeto de conexão. Caso já exista, ele simplesmente utiliza o objeto já criado através da variável FInstancia. Perceba que utilizamos propriedade e método de classe, ou seja, não será necessário instanciar o objeto previamente através do método Create.
Pronto, agora que temos a nossa conexão, vamos voltar para o formulário e inserir o seguinte código no primeiro botão:
[sourcecode language=”delphi”]
TConexao.Banco.DatabaseName := ‘c:\meubanco.fdb’;
ShowMessage(‘Banco de dados: ‘+TConexao.Banco.DatabaseName);
[/sourcecode]
Adicione ao uses a unit uConexao.
No código, através da propriedade Banco, que nos retorna a conexão, definimos o nome do banco de dados. Feito isso, mostramos uma mensagem.
Note que, toda vez que acessamos a propriedade Banco, seja na hora de definir o nome do banco de dados seja na hora de mostrar a mensagem, o método getInstancia é acionado.
Neste caso, na primeira vez o objeto é criado através do método Create. Já na segunda, como já existe o objeto, simplesmente retornamos a variável FInstancia.
Vamos para o segundo botão:
[sourcecode language=”delphi”]
TConexao.Banco.Params.Add(‘user: usuario’);
TConexao.Banco.Params.Add(‘password: senha’);
TConexao.Banco.LoginPrompt := False;
ShowMessage(‘Configurações adicionais inseridas na conexão!’);
[/sourcecode]
Aqui, simplesmente adicionamos parâmetros à conexão e definimos para não mostrar a tela de login.
E por fim, no terceiro botão mostramos os dados da conexão:
[sourcecode language=”delphi”]
edNomeBd.Text := TConexao.Banco.DatabaseName;
if TConexao.Banco.LoginPrompt then
edPrompt.Text := ‘Sim’
else
edPrompt.Text := ‘Não’;
edParametros.Lines.Assign(TConexao.Banco.Params);
[/sourcecode]
Ao executar a aplicação, o objeto será criado no primeiro clique. Já nos demais, apenas será retornado a instância do objeto já criado. E ao finalizar a aplicação, ele será destruído.
Este é um exemplo simples, mas a ideia é a mesma, mesmo para usos mais complexos. Eu não cheguei a me conectar efetivamente no banco de dados, mesmo porque este banco não existe na realidade. Vale lembrar também, que os códigos mostrados neste post serviram apenas para exemplificar o uso do padrão, como por exemplo, os códigos dos botões 1 e 2. O motivo de estarem em locais diferentes é para simular as diversas tentativas de conexões que podem ocorrer num determinado espaço de tempo.
Veja, no início eu citei o acesso de usuários. Você poderá criar um singleton do usuário e controlar desde permissões a logs (operações efetuados pelo usuário no sistema). Tudo isso, mantendo apenas um objeto instanciado.
Fico por aqui, obrigado e espero que este artigo seja útil à comunidade.
Gostaria de lembrar que, para ambientes multi-thread, você poderá usar uma seção crítica. Para isso, será necessário criar uma instância global de System.SyncObjs.TCriticalSection. Como por exemplo:
[sourcecode language=”delphi”]
var
MyLock : TCriticalSection;
implementation
uses forms;
class function TConexao.getInstancia: TConexao;
begin
If not Assigned(FInstancia) Then
begin
MyLock.Acquire;
try
If not Assigned(FInstancia) Then
FInstancia := TConexao.Create(Application);
finally
MyLock.Release;
end;
end;
Result := FInstancia;
end;
[/sourcecode]
Você deve inicializar e destruir a instância:
[sourcecode language=”delphi”]
initialization
MyLock := TCriticalSection.Create;
finalization
MyLock.Free;
[/sourcecode]
Assim, o seu singleton se torna thread safe.