No desenvolvimento de software corporativo, a repetição é o inimigo da produtividade. Escrever os mesmos métodos de Insert, Update e Delete para cada entidade do sistema (Clientes, Produtos, Fornecedores) não só consome tempo, mas aumenta a superfície de bugs.
O Delphi 13 (Athens) oferece um sistema de Generics robusto que, quando combinado com Constraints (restrições) e RTTI, permite criar uma arquitetura onde o código de persistência é escrito apenas uma vez.
Neste artigo, vamos construir um TRepository<T> capaz de manipular qualquer entidade do seu sistema, garantindo segurança de tipos em tempo de compilação.
1. O Conceito: Generics e Constraints
Para criar um repositório genérico, precisamos dizer ao compilador que o tipo T que estamos manipulando não é um tipo qualquer (como um Integer ou String), mas sim uma Classe que possui características específicas.
É aqui que entram as Constraints. Vamos utilizar duas principais:
- Inheritance Constraint (
T: TBaseEntity): Garante que o tipo genérico seja descendente de uma classe base específica. Isso nos permite acessar propriedades comuns (comoID) dentro do código genérico. - Constructor Constraint (
constructor): Garante que a classe passada possua um construtor padrão (sem parâmetros). Isso permite que o repositório instancie objetos internamente (ex:Result := T.Create).
Nota: As restrições são verificadas em tempo de compilação. Se você tentar criar um repositório para um tipo que não atenda aos requisitos, o Delphi emitirá um erro antes mesmo de executar o projeto.
2. A Base de Tudo: A Entidade Abstrata
Primeiro, definimos uma classe base. Todas as tabelas do banco de dados que quisermos manipular via repositório deverão herdar desta classe. Isso padroniza a chave primária.
type
// Classe base para todas as entidades
TBaseEntity = class
private
FId: Integer;
public
property Id: Integer read FId write FId;
end;
3. Implementando o TRepository<T>
Agora, criaremos a classe que fará a “mágica”. Utilizaremos a declaração de generics com as constraints mencionadas.
A Declaração da Classe
type
// T deve herdar de TBaseEntity E ter um construtor padrão
TRepository<T: TBaseEntity, constructor> = class
private
class var FTableName: string; // Recurso avançado: Class Var em Generics
function GetTableName: string;
protected
procedure ExecuteSQL(const ASQL: string; const AParams: array of Variant); virtual; abstract;
public
procedure Insert(AEntity: T);
procedure Update(AEntity: T);
procedure Delete(AId: Integer);
function Find(AId: Integer): T;
end;
O Poder das “Class Variables” em Generics
Um detalhe avançado e pouco utilizado é a class var dentro de um tipo genérico (referência: Class Variable in Generics).
No Delphi, TRepository<TCliente>.FTableName é uma variável distinta de TRepository<TProduto>.FTableName. O compilador gera uma variável estática para cada especialização do genérico. Usaremos isso para fazer cache do nome da tabela e evitar uso excessivo de RTTI.
uses
System.Rtti;
function TRepository<T>.GetTableName: string;
var
RttiContext: TRttiContext;
RttiType: TRttiType;
begin
// Se já descobrimos o nome da tabela para este tipo T, retorna do cache
if FTableName <> '' then
Exit(FTableName);
// Caso contrário, usa RTTI para pegar o nome da classe e remover o 'T'
RttiType := RttiContext.GetType(TClass(T));
FTableName := RttiType.Name;
// Exemplo simples: Remove o prefixo 'T' (TCliente -> Cliente)
if (FTableName.StartsWith('T')) then
Delete(FTableName, 1, 1);
Result := FTableName;
end;
4. O CRUD Genérico
Graças à constraint T: TBaseEntity, podemos acessar a propriedade Id do objeto genérico AEntity sem fazer type casting. O compilador sabe que T sempre terá um Id.
Implementação do Insert/Update
Aqui demonstramos a lógica. Em um cenário real, você usaria RTTI para iterar sobre as propriedades e montar a string SQL dinamicamente, ou usaria um framework ORM leve.
procedure TRepository<T>.Insert(AEntity: T);
var
TableName: string;
begin
TableName := GetTableName;
// A constraint nos garante que AEntity é um objeto válido
// Pseudo-código de persistência:
// ExecuteSQL('INSERT INTO ' + TableName + ' ...', [AEntity.Prop1, ...]);
WriteLn('Inserindo na tabela ' + TableName + ' o ID: ' + AEntity.Id.ToString);
end;
procedure TRepository<T>.Delete(AId: Integer);
begin
// Aqui usamos o GetTableName para saber de onde deletar
// ExecuteSQL('DELETE FROM ' + GetTableName + ' WHERE ID = :ID', [AId]);
WriteLn('Deletando da tabela ' + GetTableName + ' o registro: ' + AId.ToString);
end;
O Construtor Genérico (Find)
A constraint constructor brilha quando precisamos retornar um objeto novo, como em um método Find ou GetAll.
function TRepository<T>.Find(AId: Integer): T;
begin
// A constraint 'constructor' permite chamar T.Create
Result := T.Create;
// Graças à constraint 'TBaseEntity', podemos acessar o ID
Result.Id := AId;
// Aqui você preencheria o resto dos dados via Dataset
// LoadFromDataset(Result, DataSet);
end;
5. Utilização Prática
Veja como o código final fica limpo. Não é necessário reescrever SQL para cada nova entidade.
type
// Entidade concreta
TCliente = class(TBaseEntity)
public
Nome: string;
end;
// Repositório concreto (se necessário, ou use direto)
TClienteRepository = class(TRepository<TCliente>);
procedure TesteCadastro;
var
Repo: TRepository<TCliente>; // Declaração direta
NovoCliente: TCliente;
begin
Repo := TRepository<TCliente>.Create;
try
NovoCliente := TCliente.Create;
try
NovoCliente.Id := 10;
NovoCliente.Nome := 'Empresa X';
// O compilador valida que TCliente herda de TBaseEntity
Repo.Insert(NovoCliente);
// Busca genérica
var ClienteBusca := Repo.Find(10);
finally
NovoCliente.Free;
end;
finally
Repo.Free;
end;
end;
Resumo dos Benefícios
| Recurso | Benefício |
| Generics | Elimina a duplicação de código para métodos CRUD padrão. |
Constraint class | Garante que estamos lidando com tipos de referência, prevenindo erros de memória. |
Constraint TBaseEntity | Permite acesso “Type-Safe” a propriedades comuns (ID, DataCadastro, etc) sem Casts inseguros. |
Constraint constructor | Permite criar instâncias da entidade dentro do repositório (T.Create). |
| Class Var | Otimiza a performance fazendo cache de metadados específicos por tipo. |
Ao utilizar essas técnicas disponíveis no Delphi 13, você transforma seu código em uma arquitetura limpa, manutenível e altamente escalável.
Descubra mais sobre Régys Borges da Silveira
Assine para receber nossas notícias mais recentes por e-mail.
Dê-nos sua opinião, seu comentário ajuda o site a crescer e melhorar a qualidade dos artigos.