[Delphi] Validando propriedades de uma classe com RTTI

Olá, pessoal, como estão?
Recebi uma dúvida bem interessante dos leitores Cassiano e Jean Alysson envolvendo validações das propriedades de uma classe. Para aproveitar o momento, decidi suspender brevemente a série de artigos sobre Design Patterns para discutir sobre essa dúvida.
O maior propósito deste artigo é abordar a utilização de RTTI. Já conhece com esse recurso fantástico do Delphi?

Introdução

Uma das recomendações mais importantes do Clean Code é evitar o número excessivo de parâmetros em um método, conforme já comentei no artigo sobre a relevância da expressividade no código. Resumindo a recomendação, quando observa-se que o número de parâmetros de um método cresce para 4 ou 5 elementos, é hora de parar, analisar, e criar um objeto para armazenar estes valores, utilizando-o como um parâmetro único.

A dúvida do Cassiano e do Jean envolve este cenário. Eles modelaram uma classe com várias propriedades e utilizam uma instância dela como parâmetro, porém, precisam validar cada uma dessas propriedades nos métodos que recebem essa instância, verificando se estão devidamente preenchidas.

Para compreender essa situação, tome a seguinte classe como exemplo:

O único método público, Salvar, invoca o método privado ValidarDados que, como o próprio nome infere, valida se as propriedades possuem valores válidos. É sobre este método que discutiremos.

Em primeiro momento, poderíamos pensar na solução abaixo para codificar o método ValidarDados:

Embora funcional, não é um código nada agradável. Eu, particularmente, considero essa sequência de condições IF como repetição de código. Imagine, por exemplo, uma classe com 20 propriedades. Teríamos 20 condições IF ocupando aproximadamente 100 linhas de código em um único método. Essa quantidade de linhas, por sua vez, indicaria que o método está ferindo o princípio de Single Responsibility e deveria ser “fragmentado” em várias validações. No entanto, neste caso, teríamos 20 funções exclusivas de validação. Também não ficaria bom.

Explorando o RTTI

Para aprimorar este código, faremos uso de um recurso do Delphi chamado RTTI (Run-time Type Information), que nos permite acessar as propriedades de um objeto em tempo de execução, obtendo seus respectivos valores.

Notaram que coloquei todas as properties com visibilidade published? Essa configuração na classe é necessária para que o RTTI consiga “enxergar” as propriedades. Veja, abaixo, a mesma regra de validação após empregar este recurso:

Com esse código, as validações serão executadas para todas as propriedades que um objeto possui, independentemente da quantidade. Logo, novas propriedades adicionadas à classe serão automaticamente consideradas na validação sem a necessidade de alterar o código.

Atente-se aos métodos GetPropList (para preencher a lista de propriedades) e GetPropValue (para obter o valor de uma propriedade), que são essenciais para trabalhar com RTTI.

Apesar de já funcionar como desejamos, um pequeno problema surge com essa instrução:

O valor de Name sempre será o nome literal da propriedade. Portanto, caso a data de vencimento não esteja preenchida, essa será a mensagem retornada para o usuário:

O mesmo acontece para a descrição do documento:

Podemos alterar o nome das propriedades para DataVencimento e DescricaoDocumento, mas não é possível adicionar espaços ou caracteres especiais. De qualquer forma, a mensagem ficará “estranha” aos olhos de quem utiliza o sistema ou, na melhor das hipóteses, apenas cômico. Precisaríamos, então, de algum recurso para indicar que aquela propriedade possui uma descrição específica e acessá-la durante as validações.

Custom Attributes

Pois bem, no Delphi, essa demanda é preenchida com um recurso conhecido como Custom Attributes, que possibilita incorporar “características” à propriedades para depois acessá-las via RTTI. Adicionaremos, então, uma descrição mais clara para cada propriedade, resultando em mensagens mais amigáveis.

Para criar um atributo, seguimos o mesmo padrão de criação de uma classe, salvo a exceção de que essa estrutura deve obrigatoriamente herdar de TCustomAttribute:

Estrutura bem simples. O construtor do atributo recebe um parâmetro que será atribuído a um membro privado:

O próximo passo é adicionar o atributo na linha superior de cada propriedade, informando a descrição como parâmetro:

O terceiro passo é tirar proveito dos recursos avançados de RTTI, consumindo classes específicas para acessar as informações que desejamos. Essas classes, bem como o conceito de Custom Attributes, estão disponíveis desde a versão 2010 do Delphi.

Confira as alterações no método ValidarDados:

Um pouco mais de RTTI

As classes que iniciam com “TRtti” – como TRttiContext, TRttiType e TRttiProperty – fazer parte da unit RTTI.pas e são exclusivas para trabalhar com acesso às informações de uma classe em tempo de execução. Veja que, quando o valor de uma propriedade não está preenchido, utilizamos o TRttiType.GetAttributes para ler os atributos daquela propriedade e acessar o texto que configuramos através do nosso Custom Attribute, ou melhor, TDescricao. Observe também que o código espera que uma propriedade possa ter vários atributos – por isso executamos um loop em GetAttributes.

Um detalhe muito importante é que, com essa técnica, as properties não precisam obrigatoriamente ser published. Na verdade, se mantermos elas com essa visibilidade, o compilador levantará o seguinte Warning:

Configure-as com visibilidade public para evitar este aviso do compilador.

Um pouco mais de Custom Attributes

Bom, entendo que agora já temos a solução, mas gostaria de ir um pouco mais além com o Custom Attributes.

Criaremos um tipo de atributo, chamado TValidador, que possui uma função própria de validação, recebendo a propriedade (do tipo TRttiProperty) como parâmetro.

Já que movemos a validação para o próprio atributo, o método ValidarDados naturalmente ficará menor:

 

Isso aí pessoal! Apresentei 4 formas de validar o conteúdo das propriedades de uma classe, mas, claro, recomendo as duas últimas, principalmente pela possibilidade de compartilhar este código com outras classes da arquitetura, inclusive usando Generics!

Vale destacar que o exemplo deste artigo é apenas uma parcela do que podemos alcançar com o RTTI. Aproveite e acesse o Wiki da Embarcadero na seção Working with RTTI e conheça mais sobre este recurso!

Grande abraço!


André Celestino