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:
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á.
Olá Luiz,
Não vai mais dá continuidade no artigo?
Grato