Criando sua API de validação
Proposta
Neste workshop veremos como criar, passo a passo, sua própria API de validação. Para isso definiremos primeiro as propriedades de uma API deste tipo e utilizaremos o padrão Composite Object para aumentar a reutilização da API.
O que é Validar ?
Validar significa testar que algo é o que deveria ser. Normalmente significa verificar que um dado valor é diferente de algum valor fixo ou está em um intervalo de valores.
O resultado de uma validação não é a apenas a indicação de que o valor está valido ou não, mas principalmente a razão de porquê não está válido. Isto significa que nossos validadores não podem simplesmente retornar "sim" ou "não" mas terão que retornar um objeto que informe porque o valor é invalido. Se for válido, obviamente não precisamos informar mais nada.
O modelo
O nosso modelo é bem simples. Definimos um validador como uma interface a ser implementada
conforme a lógica de validação. O validador têm um método validate que recebe o objeto a ser validado
e retorna o resultado da validação no objeto ValidationResult.
Faremos o validador aceitar um objeto genérico qualquer fortemente tipados. Veremos depois a utilidade
desta funcionalidade e o porquê de não usar Object directamente.
public interface Validator<T> {
public ValidationResult validate(T object);
}
Como vimos, apenas precisamos de informar as razões de porquê o valor não é válido.
Precisamos apenas acrescentar um objeto InvalidationReason ao resultado, tornando o objeto
ValidationResult uma coleção de InvalidationReason
public class ValidationResult implements Iterable<InvalidationReason> {
private final List<InvalidationReason> reasons = new LinkedList<InvalidationReason>();
public Iterator<InvalidationReason> getIterator(){
return reasons.iterator();
}
public void addReason(InvalidationReason reason){
reasons.add(reason);
}
public void removeReason(InvalidationReason reason){
reasons.remove(reason);
}
}
Contudo, apenas a razão não é suficiente. Imaginemos uma situação em que o validador não consegue decidir se o valor é válido ou não. Por exemplo, um validador de email tenta connectar-se a um servidor DNS para verificar que o dominio do email existe. Se o validador não poder fazer essa ligação, ele não pode dizer que o email é válido, mas também não pode dizer que não é. Nesta circunstância o validador precisa informar a duvida. A forma de fazer isso é definir um grau de severidade para a razão de invalidação.
public interface InvalidationReason {
public InvalidationSeverity getSeverity();
public String getMessage();
public Object[] getParams();
}
public enum InvalidationSeverity {
SEVERE,
WARNING
}
Definimos InvalidationReason como uma interface para termos o máximo de flexibilidade na implementação.
O método getMessage() retorna uma mensagem e um método getParams os parametros da mensagem.
Isto serve para podermos criar mensagem corretamente internacionalizadas e deixa a UI se preocupar com o texto real
e sua apresentação.
Faltou apenas decidir como sabemos que o valor está válido ou não. Sabemos que ele não está válido se existir uma razão
severa para a invalidação. Então, em vez deverificarmos isso a todo o momento, vamos criar um método, isValid(), que faz esse trabalho.
public class ValidationResult implements Iterable<InvalidationReason> {
private final List<InvalidationReason> reasons = new LinkedList<InvalidationReason>();
// o mesmo codigo de antes
public boolean isValid(){
for (InvalidationReason reason : reasons){
if (reason.getSeverity().equals(InvalidationSeverity.SEVERE){
return false;
}
}
return true;
}
}
Este método obriga a iterar todas as razões para verificar se existe alguma com severidade SEVERE. Não é muito
eficaz. Se tivessemos um Map seria bem mais facil...