Revendo o que já foi feito – Que tal um ORM Básico? Parte 12

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á.

Sobre o autor: Luiz Carlos (60 Posts)

Desenvolvedor de software Balsas/MA


Compartilhe:

One Reply to “Revendo o que já foi feito – Que tal um ORM Básico? Parte 12”

Deixe uma resposta

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