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:

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;

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:

TConexao.Banco.DatabaseName := 'c:\meubanco.fdb';
  ShowMessage('Banco de dados: '+TConexao.Banco.DatabaseName);

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:

  TConexao.Banco.Params.Add('user: usuario');
  TConexao.Banco.Params.Add('password: senha');
  TConexao.Banco.LoginPrompt := False;
  ShowMessage('Configurações adicionais inseridas na conexão!');

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:

  edNomeBd.Text := TConexao.Banco.DatabaseName;
  if TConexao.Banco.LoginPrompt then
    edPrompt.Text := 'Sim'
  else
    edPrompt.Text := 'Não';
  edParametros.Lines.Assign(TConexao.Banco.Params);

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.

Desenvolvedor de software desde 1995. Em 1998, abriu sua própria empresa, a Lukas Sistemas, desde então passou a atender diversas empresas, principalmente autopeças. Apaixonado por Delphi, porém não o impede de flertar com outras linguagens sempre que possível. Mora na cidade de Balsas/MA com sua esposa e dois filhos.

One thought on “Design Pattern: Singleton”

  1. 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:

    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;
    

    Você deve inicializar e destruir a instância:

    initialization
      MyLock := TCriticalSection.Create;
    finalization
      MyLock.Free;
    

    Assim, o seu singleton se torna thread safe.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.