Você já se deparou com aquele botão “Salvar” que contém dezenas de linhas de if EditNome.Text = '' then ou if Length(EditCPF.Text) <> 11 then? Esse tipo de código, conhecido como Spaghetti Code, mistura regras de validação com a lógica de interface (UI), tornando o sistema difícil de manter e testar.
Neste artigo, vamos explorar como o RTTI (Run-Time Type Information) e os Atributos Customizados (Custom Attributes) do Delphi podem transformar essa bagunça em um código limpo, declarativo e elegante.
O Conceito: Programação Declarativa
Em vez de escrever como validar cada campo imperativamente no clique do botão, nós vamos declarar as regras diretamente nas propriedades da nossa classe de domínio.
O objetivo é transformar isso:
// Abordagem Tradicional (Imperativa)
if Cliente.Nome = '' then
raise Exception.Create('O nome é obrigatório');
if not ValidarCPF(Cliente.CPF) then
raise Exception.Create('CPF inválido');
Nisso:
// Abordagem com Atributos (Declarativa)
type
TCliente = class
private
FNome: string;
FCPF: string;
public
[TObrigatorio('O nome é obrigatório')]
property Nome: string read FNome write FNome;
[TObrigatorio, TValidaCPF]
property CPF: string read FCPF write FCPF;
end;
Para fazer essa “mágica” acontecer, precisamos de três pilares:
- Atributos Customizados: As etiquetas que colamos nas propriedades.
- RTTI: O mecanismo que lê essas etiquetas em tempo de execução.
- Classe Validadora: O motor que processa as regras.
Passo 1: Criando os Atributos
Primeiro, vamos criar uma classe base abstrata para todos os nossos validadores. Isso garante polimorfismo e facilita a criação de novas regras no futuro.
unit U.Attributes;
interface
uses
System.Rtti;
type
// Classe base para atributos de validação
TValidadorAttribute = class(TCustomAttribute)
private
FMensagemErro: string;
public
constructor Create(const AMensagem: string = '');
// Método abstrato que cada regra específica deve implementar
function Validar(Value: TValue): Boolean; virtual; abstract;
property MensagemErro: string read FMensagemErro;
end;
// Validador de Campo Obrigatório
TObrigatorioAttribute = class(TValidadorAttribute)
public
function Validar(Value: TValue): Boolean; override;
end;
// Validador de CPF (Simplificado para o exemplo)
TValidaCPFAttribute = class(TValidadorAttribute)
public
function Validar(Value: TValue): Boolean; override;
end;
implementation
uses
System.SysUtils;
{ TValidadorAttribute }
constructor TValidadorAttribute.Create(const AMensagem: string);
begin
FMensagemErro := AMensagem;
end;
{ TObrigatorioAttribute }
function TObrigatorioAttribute.Validar(Value: TValue): Boolean;
begin
// Verifica se é string e se está vazia, ou se é objeto nulo, etc.
if Value.Kind in [tkString, tkUString, tkWString, tkLString] then
Result := Trim(Value.AsString) <> ''
else if Value.Kind = tkInteger then
Result := Value.AsInteger > 0
else
Result := not Value.IsEmpty;
if (not Result) and (FMensagemErro = '') then
FMensagemErro := 'Este campo é obrigatório.';
end;
{ TValidaCPFAttribute }
function TValidaCPFAttribute.Validar(Value: TValue): Boolean;
begin
// Aqui iria sua rotina completa de cálculo de CPF (Mod11)
// Para fins didáticos, vamos validar apenas o tamanho
Result := Length(OnlyNumbers(Value.AsString)) = 11;
if (not Result) and (FMensagemErro = '') then
FMensagemErro := 'O CPF informado é inválido.';
end;
end.
Passo 2: O Motor de Validação (RTTI)
Agora precisamos de uma classe utilitária que receba qualquer objeto, varra suas propriedades e verifique se alguma delas possui nossos atributos.
unit U.ValidadorGeneric;
interface
uses
System.Rtti, System.Generics.Collections, U.Attributes;
type
TValidador = class
public
// Retorna uma lista de erros. Se a lista for vazia, está válido.
class function Validar(Objeto: TObject): TList<string>;
end;
implementation
uses
System.SysUtils;
class function TValidador.Validar(Objeto: TObject): TList<string>;
var
Ctx: TRttiContext;
RttiType: TRttiType;
Prop: TRttiProperty;
Attr: TCustomAttribute;
ValidadorAttr: TValidadorAttribute;
Value: TValue;
begin
Result := TList<string>.Create;
Ctx := TRttiContext.Create;
try
// Obtém informações do tipo do objeto passado
RttiType := Ctx.GetType(Objeto.ClassType);
// Itera sobre todas as propriedades da classe
for Prop in RttiType.GetProperties do
begin
// Itera sobre todos os atributos da propriedade
for Attr in Prop.GetAttributes do
begin
// Verifica se o atributo é do tipo TValidadorAttribute
if Attr is TValidadorAttribute then
begin
ValidadorAttr := TValidadorAttribute(Attr);
// Obtém o valor atual da propriedade na instância do objeto
Value := Prop.GetValue(Objeto);
// Executa a validação
if not ValidadorAttr.Validar(Value) then
begin
Result.Add(Format('Propriedade "%s": %s', [Prop.Name, ValidadorAttr.MensagemErro]));
end;
end;
end;
end;
finally
Ctx.Free;
end;
end;
end.
Passo 3: Colocando em Prática
Agora, o uso no dia a dia torna-se extremamente simples. Vamos supor que você tenha um formulário de cadastro.
1. A Classe de Modelo
type
TUsuario = class
private
FNome: string;
FEmail: string;
FIdade: Integer;
public
[TObrigatorio('Por favor, informe o nome do usuário.')]
property Nome: string read FNome write FNome;
[TObrigatorio]
property Email: string read FEmail write FEmail;
[TObrigatorio('A idade deve ser maior que zero.')]
property Idade: Integer read FIdade write FIdade;
end;
2. O Botão Salvar
procedure TFormCadastro.BtnSalvarClick(Sender: TObject);
var
Usuario: TUsuario;
Erros: TList<string>;
Erro: string;
begin
Usuario := TUsuario.Create;
Erros := nil;
try
// Populando o objeto (geralmente via LiveBindings ou manual)
Usuario.Nome := EditNome.Text;
Usuario.Email := EditEmail.Text;
Usuario.Idade := StrToIntDef(EditIdade.Text, 0);
// VALIDAÇÃO CENTRALIZADA
Erros := TValidador.Validar(Usuario);
if Erros.Count > 0 then
begin
ShowMessage('Foram encontrados os seguintes erros:' + sLineBreak +
string.Join(sLineBreak, Erros.ToArray));
Exit; // Interrompe o salvamento
end;
// Se chegou aqui, está tudo válido!
ShowMessage('Usuário salvo com sucesso!');
// Usuario.SalvarNoBanco;
finally
Usuario.Free;
Erros.Free;
end;
end;
Vantagens desta Abordagem
- Reutilização: O validador de CPF é escrito uma única vez e aplicado em qualquer classe (
TCliente,TFornecedor,TFuncionario) apenas adicionando[TValidaCPF]. - Legibilidade: Ao olhar para a classe
TUsuario, você entende imediatamente as regras de negócio sem precisar ler código de implementação. - Desacoplamento: A interface gráfica não conhece as regras de validação. Se a regra do CPF mudar, você altera apenas o atributo, sem tocar nos formulários.
- Padronização: Todas as mensagens de erro e validações seguem o mesmo fluxo.
Conclusão
O uso de RTTI e Atributos no Delphi é uma ferramenta poderosa para reduzir a complexidade ciclomática do seu código. Embora haja um pequeno custo de performance devido ao uso de reflexão (RTTI), em cenários de validação de formulários e regras de negócio (CRUD), esse impacto é imperceptível diante do ganho gigantesco em manutenibilidade e limpeza de código.
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.