Olá Pessoal

Hoje iremos analisar as classes criadas e refatorar o que for necessário. Só depois, iremos para a implementação da classe do DbExpress que eu havia prometido no post anterior. Assim, evitamos que problemas existentes agora persistam nas próximas classes.

Desde o início nos preocupamos em seguir os princípios da Orientação a Objetos (OO) em nosso projeto. Porém, estar totalmente dentro destes princípios por vezes pode se tornar uma tarefa não tão simples, o que poderá fazer com que um código que não se enquadre nos “bons costumes” OO passe despercebido aos nossos olhos em meio aos inúmeros métodos e classes por nós criados. Por isso, sempre procurar melhorar o código (refatorar) é a melhor maneira para tentar atingir nosso objetivo. E é isso que faremos adiante.

Dando início à análise, tem uma coisa que não está cheirando bem nos métodos CRUD: Inserir, Excluir, Salvar e Buscar. Isso ficou ainda mais evidente com a criação da segunda classe, a TDaoIBX. Se focarmos no método Inserir das duas classes (TDaoUib e TDaoIbx), percebemos uma repetição de código exagerada:

orm-comparaMetodos

Princípio OO: Encapsule o que varia

Primeiro, só pra constar e não deixar qualquer dúvida, o método Inserir tem o seguinte objetivo:

Gerar uma string SQL para a inserção dos dados na tabela.

Desta forma, o padrão Sql de inserção segue o seguinte formato:

  • Insert into Tabela (campo-1, campo-2, campo-3…) values (:campo-1, :campo-2, :campo-3…);

Veja que não tem dados específicos do componente de acesso, ou seja, trata-se apenas de uma string. Portanto, não precisamos replicar o código do SQL em todas as classes de conexão, a não ser que em determinada classe o padrão seja diferente deste. Neste caso, bastará um override no método, visto que o mesmo é virtual. Até o momento, é suficiente dizer que o padrão é o descrito acima.

Entendido qual o objetivo do método, iremos agora retirar os códigos SQL dos métodos CRUD, movendo-os para a classe base, ou seja, nossa classe abstrata TDaoBase . Vamos lá!

Abra Base.pas e localize a classe TDaoBase. Em protected, adicione quatro novos métodos:

...
 protected
    //geração do sql padrao
    function GerarSqlInsert (ATabela: TTabela; TipoRtti: TRttiType): string; virtual;
    function GerarSqlUpdate (ATabela: TTabela; TipoRtti: TRttiType): string; virtual;
    function GerarSqlDelete (ATabela: TTabela): string; virtual;
    function GerarSqlSelect (ATabela: TTabela): string; virtual;
...

Ctrl+Shift+C para implementar os métodos. Abaixo, métodos implementados:

function TDaoBase.GerarSqlDelete(ATabela: TTabela): string;
var
  Campo, Separador: string;
  ASql : TStringList;
begin
  ASql := TStringList.Create;
  try
    with ASql do
    begin
      Add('Delete from ' + PegaNomeTab(ATabela));
      Add('Where');
      Separador := '';
      for Campo in PegaPks(ATabela) do
      begin
        Add(Separador + Campo + '= :' + Campo);
        Separador := ' and ';
      end;
    end;
    Result := ASql.Text;
  finally
    ASql.Free;
  end;
end;

function TDaoBase.GerarSqlInsert(ATabela: TTabela; TipoRtti: TRttiType): string;
var
  Separador: string;
  ASql : TStringList;
  PropRtti: TRttiProperty;
begin
  ASql := TStringList.Create;
  try
    with ASql do
    begin
      Add('Insert into ' + PegaNomeTab(ATabela));
      Add('(');

      //campos da tabela
      Separador := '';
      for PropRtti in TipoRtti.GetProperties do
      begin
        Add(Separador + PropRtti.Name);
        Separador := ',';
      end;
      Add(')');

      //parâmetros
      Add('Values (');
      Separador := '';
      for PropRtti in TipoRtti.GetProperties do
      begin
        Add(Separador + ':' + PropRtti.Name);
        Separador := ',';
      end;
      Add(')');
    end;
    Result := ASql.text;
  finally
    ASql.Free;
  end;
end;

function TDaoBase.GerarSqlSelect(ATabela: TTabela): string;
var
  Campo, Separador: string;
  ASql : TStringList;
begin
  ASql := TStringList.Create;
  try
    with ASql do
    begin
      Add('Select * from ' + PegaNomeTab(ATabela));
      Add('Where');
      Separador := '';
      for Campo in PegaPks(ATabela) do
      begin
        Add(Separador + Campo + '= :' + Campo);
        Separador := ' and ';
      end;
    end;
    Result := ASql.Text;
  finally
    ASql.Free;
  end;
end;

function TDaoBase.GerarSqlUpdate(ATabela: TTabela; TipoRtti: TRttiType): string;
var
  Campo, Separador: string;
  ASql : TStringList;
  PropRtti: TRttiProperty;
begin
  ASql := TStringList.Create;
  try
    with ASql do
    begin
      Add('Update ' + PegaNomeTab(ATabela));
      Add('set');

      //campos da tabela
      Separador := '';
      for PropRtti in TipoRtti.GetProperties do
      begin
        Add(Separador + PropRtti.Name + '=:'+PropRtti.Name);
        Separador := ',';
      end;
      Add('where');

      //parâmetros da cláusula where
      Separador := '';
      for Campo in PegaPks(ATabela) do
      begin
        Add(Separador+ Campo + '= :' + Campo);
        Separador := ' and ';
      end;
    end;
    Result := ASql.text;
  finally
    ASql.Free;
  end;
end;

Basicamente, movi a parte de cada método que tratava da geração da string SQL. Feito isso, você pode estar curioso com relação aos métodos CRUD em TDaoUib e TDaoIbx. Não se aflija, segue abaixo métodos de TDaoUib:

function TDaoUib.Excluir(ATabela: TTabela): Integer;
var
  Comando: TFuncReflexao;
begin
  //crio uma variável do tipo TFuncReflexao - um método anônimo
  Comando := function(ACampos: TCamposAnoni): Integer
  var
    Campo: string;
    PropRtti: TRttiProperty;
  begin
    Qry.Close;
    Qry.SQL.Clear;
    Qry.SQL.Text := GerarSqlDelete(ATabela);

    //percorrer todos os campos da chave primária
    for Campo in PegaPks(ATabela) do
    begin
      // setando os parâmetros
      for PropRtti in ACampos.TipoRtti.GetProperties do
        if CompareText(PropRtti.Name, Campo) = 0 then
          begin
            ConfiguraParametro(PropRtti, Campo, ATabela, Qry);
          end;
    end;
    Result := ExecutaQuery;
  end;

  //reflection da tabela e execução da query preparada acima.
  Result := ReflexaoSQL(ATabela, Comando);
end;

function TDaoUib.Inserir(ATabela: TTabela): Integer;
var
  Comando: TFuncReflexao;
begin
  Result := 0;
  Comando := function(ACampos: TCamposAnoni): Integer
  var
    Campo: string;
    PropRtti: TRttiProperty;
  begin
    with Qry do
    begin
      close;
      SQL.clear;
      SQL.Text := GerarSqlInsert(ATabela, ACampos.TipoRtti);

      //valor dos parâmetros
      for PropRtti in ACampos.TipoRtti.GetProperties do
      begin
        Campo := PropRtti.Name;
        ConfiguraParametro(PropRtti, Campo, ATabela, Qry);
      end;
    end;
    Result := ExecutaQuery;
  end;

  //reflection da tabela e execução da query preparada acima.
  Result := ReflexaoSQL(ATabela, Comando);
end;

function TDaoUib.Salvar(ATabela: TTabela): Integer;
var
  Comando: TFuncReflexao;
begin
  Result := 0;
  Comando := function(ACampos: TCamposAnoni): Integer
  var
    Campo: string;
    PropRtti: TRttiProperty;
  begin
    with Qry do
    begin
      close;
      sql.Clear;
      sql.Text := GerarSqlUpdate(ATabela, Acampos.TipoRtti);
      //valor dos parâmetros
      for PropRtti in ACampos.TipoRtti.GetProperties do
      begin
        Campo := PropRtti.Name;
        ConfiguraParametro(PropRtti, Campo, ATabela, Qry);
      end;
    end;
    Result := ExecutaQuery;
  end;

  //reflection da tabela e execução da query preparada acima.
  Result := ReflexaoSQL(ATabela, Comando);
end;

function TDaoUib.Buscar(ATabela: TTabela): Integer;
var
  Comando: TFuncReflexao;
  Dados: TUIBQuery;
begin
  Dados := TUIBQuery.Create(nil);
  try
    //crio uma variável do tipo TFuncReflexao - um método anônimo
    Comando := function(ACampos: TCamposAnoni): Integer
    var
      Campo: string;
      PropRtti: TRttiProperty;
    begin
      with Dados do
      begin
        Database := FDatabase;
        Transaction := FTransaction;
        sql.Text := GerarSqlSelect(ATabela);

        for Campo in ACampos.PKs do
        begin
          // setando os parâmetros
          for PropRtti in ACampos.TipoRtti.GetProperties do
            if CompareText(PropRtti.Name, Campo) = 0 then
              begin
                ConfiguraParametro(PropRtti, Campo, ATabela, Dados);
              end;
        end;
        Open;
        Result := Fields.RecordCount;
        if Result > 0 then
        begin
          for PropRtti in ACampos.TipoRtti.GetProperties do
          begin
            Campo := PropRtti.Name;
            SetaDadosTabela(PropRtti, Campo, ATabela, Dados);
          end;
        end;
      end;
    end;

    //reflection da tabela e abertura da query preparada acima.
    Result := ReflexaoSQL(ATabela, Comando);
  finally
    Dados.Free;
  end;
end;

Note que, tudo que era relacionado à formatação da string SQL foi retirado e em seu lugar foi colocado os novos métodos da classe Base. O mesmo deverá ser feito para a classe TDaoIbx.

Neste post não deixarei os fontes, visto que ainda falta finalizar as mudanças iniciadas acima.

No próximo, continuaremos os trabalhos de refatoração do código. Até lá.

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 “Revendo o que já foi feito – Que tal um ORM Básico? Parte 12”

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.