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.
