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!
- Dont' repeat yourself, don't repeat yourself, don't repeat yourself - eu repetia em minha mente cada vez que esbarrava em mais uma 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 |
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 ); static; end; implementation procedure TSqlQuery.ForEachRow(const sql: string; callback: TSqlQueryRowCallback); var qry : TSqlQuery; begin qry := TSqlQuery.Create; qry.Close; qry.CommandText := sql; try qry.Open; while not qry.Eof do begin callback; qry.Next; end; finally qry.Close; FreeAndNil(qry); 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 TSqlQry.ForEachRow('select some fields from some table',
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.