Refatoração com  Class Interceptors e Métodos Anônimos
Autor: Fabiano Salles | Publicado: 2015-06-22T15:31:00-03:00



Quando assunto é manipulação de bancos de dados, há poucas ferramentas tão práticas e convenientes quanto os DataSets do Delphi. Instacie um dataset qualquer, informe uma consulta sql, chame o método open e bingo! temos uma cópia dos registros do banco (e uma infinidade de metadados) em memória, prontos para serem manipulados.

procedure Foo;
var
  qry: TSqlQuery;
begin
  qry := TSqlQuery.Create;
  try
    qry.CommandText := 'select some fields from some table';
    qry.Open;
    while not qry.Eof do
    begin
      //processa o registro atual..
      //e avança para o próximo
      qry.Next;
   end;
   finally
     qry.Close;
     FreeAndNil(qry);
   end;
end;


Prático e simples, não é?

Pois bem! Image dar manutenção em um sistema onde há milhares de funções exatamente iguais a esta, espalhadas por centenas de units e você começará sentir calafrios.


Há algo muito errado aqui!

&nbsp;- Dont' repeat yourself, don't repeat yourself, don't repeat yourself - eu repetia em minha mente cada vez que esbarrava em mais numa cópia deste código.

Normalmente, num caso destes, a saída é bem simples: criar uma classe descentente de TSqlQuery e implementar um método que encapsule a lógica de criação,abetura, navegação, fechamento e liberação da query. Mas, para funcionar, é preciso alterar todas as ocorrências de TSqlQuery para a nova classe criada e isso implicaria mais trabalho chato, repetitivo e sujeito a erros.



Class Interceptors



Interceptor Design Pattern

A sintaxe do object pascal (ou delphi language, como foi rebatizada nas versões mais recentes), permite declarar classes com o mesmo nome se elas estiverem em units diferentes. Quando isto ocorrer, a segunda classe irá interceptar a primeira e irá extendê-la, no escopo de sua unit, combinando as duas classes em uma só.

Com isto em mente, resolver o problema do código repetido acima fica mais fácil. Basta interceptar a classe TSqlQuery nas units em que eu estou trabalhando e extendê-las com uma função mais inteligente para iterar sobre seus dados.

Veja o código abaixo:

unit SQLExprInterceptor;

interface

uses
  SqlExpr;

type
  TSqlQueryRowCallback = reference to procedure;
  TSqlQuery = class( SqlExpr.TSqlQuery )
  public
    procedure ForEachRow( const sql : string; callback: TSqlQueryRowCallback ); overload;
  end;

implementation

procedure TSqlQuery.ForEachRow(const sql: string; callback: TSqlQueryRowCallback);
begin
  self.Close;
  self.CommandText := sql;
  try
    self.Open;
    while not self.Eof do begin
      callback;
      self.Next;
    end;
  finally
    self.Close;
  end;
end;

end.

A classe TSqlQuery foi declarada como um referência para a classe original ( que está dentro da unit SqlExpr )


TSqlQuery = class( SqlExpr.TSqlQuery )

e extendemos sua declaração com um método que recebe um sql e uma referência para nosso callback, cuja assintatura é de um método simples, sem parâmetros.

TSqlQueryRowCallback = reference to procedure;

Agora, com a ajuda dos métodos anônimos, podemos eliminar todo o código repetitivo do início do texto e substituí-lo pelo código abaxo:

procedure Foo;
begin
   qry.ForEachRow('select some fields from some table',&nbsp;
     procedure begin
        //processa o registro atual...
     end);
end;

Agora sim! Está muito melhor. Note que o único requisito para que os class interceptors funcionem, é que a referência à classe original apareça antes da unit que contém as extensões para a classe.

Abraços.