Validar estado de um objeto

Na ultima Mundo Java saiu um artigo sobre Hibernate Validator.
Antes da dúvida vamos às classes do exemplo na revista.
O objeto a ser validado era este:

[code]public class Carro {

String cor;
@AssertTrue
boolean ipvaPago;
@AssertFalse
boolean amassado;

}[/code]
A classe que realiza o teste esta:

[code]public class AssertTest {

public static void main(String[] args) {
	
	Carro fuscao = new Carro();
	fuscao.cor = "preto";
	fuscao.ipvaPago = false;
	fuscao.amassado = true;
	
	ClassValidator<Carro> classValidator = new ClassValidator<Carro>(Carro.class);
	InvalidValue[] invalidValues = classValidator.getInvalidValues(fuscao);
	HandlerTest.printErrors(invalidValues);
	
	fuscao.ipvaPago = true;
	fuscao.amassado = false;
	invalidValues = classValidator.getInvalidValues(fuscao);
	HandlerTest.printErrors(invalidValues);
}

}[/code]
E aqui a classe que simplismente exibe os erros:

[code]public class HandlerTest {

public static void printErrors(InvalidValue[] invalidValues)
{
	System.out.println("** Listagem de erros **");
	if(invalidValues.length == 0)
		System.out.println("Oba! Não há erros\n");
	else
		print(invalidValues);
}

private static void print(InvalidValue[] invalidValues) 
{
	Class clazz = invalidValues[0].getBeanClass();
	System.out.print("Há " + invalidValues.length);
	System.out.println(" erro(s) na entidade: " + clazz.getSimpleName());
	for(InvalidValue iv : invalidValues)
	{
		System.out.print("A propriedade ");
		System.out.print(iv.getPropertyName());
		System.out.println(" possui o seguinte erro: ");
		System.out.println(iv.getMessage());
		if(iv.getValue() != null)
		{
			System.out.print("Valor errado: ");
			System.out.println(iv.getValue());
		}
	}
}

}[/code]

Claro que o Clayton, autor do artigo, simplificou seus exemplos, mas vamos ao que interessa.

Um carro possue seu estado válido quando o ipva está pago e não possui nenhum amassado.
A verificação do estado de um objeto Carro não deveria ser responsabilidade do mesmo?
Assim, ao invés das validações na classe AssertTest, eu simplismente perguntaria ao objeto se ele é valido.
Para ficar mais claro, aqui estão os fontes de como imaginei que seria resumidamente:

[code]public class Car {

private InvalidValue[] invalidValues;

String cor;
@AssertTrue
boolean ipvaPago;
@AssertFalse
boolean amassado;

public void save()
{
	System.out.println("Carro salvo!");
}

public void checkState()
{
	ClassValidator<Car> classValidator = new ClassValidator<Car>(Car.class);
	invalidValues = classValidator.getInvalidValues(this);
	if(!(invalidValues == null) && !(invalidValues.length == 0))
		throw new IllegalStateException(getErrors());
}

private String getErrors()
{
	StringBuilder errors = new StringBuilder();
	for(InvalidValue iv : invalidValues)
	{
		errors.append("\n").append(iv.getMessage())
			.append(" (").append(iv.getPropertyName())
			.append(" = ").append(iv.getValue()).append(")");
	}
	return errors.toString();
}

}[/code]

[code]public class AssertTest {

public static void main(String[] args) {
	
	Car fuscao = new Car();
	fuscao.cor = "preto";
	
	fuscao.ipvaPago = true;
	fuscao.amassado = false;
	
	fuscao.checkState();
	fuscao.save();
	
	fuscao.ipvaPago = false;
	fuscao.amassado = true;
	
	fuscao.checkState();
	fuscao.save();
}

}[/code]

E ai? Faz algum sentido? Quais os prós e contras?

Concordo com seu ponto de vista.

Aqui no trabalho tivemos essa discussão há algum tempo. No final resolvemos fazer uma implementação como a da interface Comparator e a classe Collections

Criamos a interface Validable e a classe Validator.
A interface contém o método validade() que retorna um boolean indicando se o estado da classe é válido ou não.
A classe possui um método estático validate(T entidade, Validable<? super T> validacaoAlternativa) que funciona como o sort(List list, Comparator<? super T> c) da classe Collections.

Assim a validação da entidade é feita por ela, mas pode ser modificada para os casos particulares.

Não. Embora “validar o estado do carro” seja muito simplificado no artigo o ponto é que a condição de validação
e o objecto validador não são/estão no carro.
No mundo real quem avalia carros não são carros, são pessoas. E não são pessoas quaisquer, são pessoas que conhecem do assunto. A responsabilidade do carro não é saber o seu estado, isso é responsabilidade do seu dono, do mecanico ou de outra pessoa “especialista”. Além disso as regras de validação podem ser bem mais complexas do que o artigo exemplifica.

Resumindo, o principio de separação de responsabilidade faz com que tenha que haver outro objeto que avalie o carro.

[quote=sergiotaborda] Embora “validar o estado do carro” seja muito simplificado no artigo o ponto é que a condição de validação e o objecto validador não são/estão no carro.

Resumindo, o principio de separação de responsabilidade faz com que tenha que haver outro objeto que avalie o carro.[/quote]

Tudo bem, porém quando eu olho este exemplo:

[code]public class Carro {

String cor;   
@AssertTrue   
boolean ipvaPago;   
@AssertFalse   
boolean amassado;   

} [/code]

O @AssertTrue do atributo ipvaPago me parece uma condição.

Nesse caso é apenas um parâmetro para uma condição realmente externa, no caso um AND nesses dois asserts?

No caso o AvaliadorDeCarro deveria ter um comportamento avaliar(Carro) que iria fazer uma avaliação do estado do objeto Carro. Isso, se esta modelagem fizer sentido para o seu sistema.
Pode ser que não faça sentido modelar um AvaliadorDeCarro.

O que eu havia entendido era validar o estado do objeto, informando que a cor não pode ser “Abóbora com ondas em verde limão nas laterais”, por exemplo.
Ou que no modelo XPTO a cor “Cinza com raios amarelos nas laterais” só é permitida se você comprar o “Kit Tunning 2007”.

Isto é, validar no sentido de verificar se o estado, os atributos, do objeto instanciado estão em conformidade com as regras de negócio e restrições modeladas para a entidade.

[quote=sergiotaborda]Além disso as regras de validação podem ser bem mais complexas do que o artigo exemplifica.

Resumindo, o principio de separação de responsabilidade faz com que tenha que haver outro objeto que avalie o carro.[/quote]
No caso que eu exemplifiquei acima, complexidade de uma validação não tem como fugir da classe que o estado, está sendo validado.

No caso o AvaliadorDeCarro deveria ter um comportamento avaliar(Carro) que iria fazer uma avaliação do estado do objeto Carro. Isso, se esta modelagem fizer sentido para o seu sistema.
Pode ser que não faça sentido modelar um AvaliadorDeCarro.
[/quote]

Se vc deseja validar o Carro em alguma forma, sempre faz sentido usar outro objeto para isso: Principio da Separação de Responsabilidade.

Repare, além de tudo as regras de avaliação podem mudar, podem aumentar. Vc pode querer usar diferentes estratégias de avaliação com base noutros parâmetros.
Se vc joga isso dentro do própio carro não tem flexibilidade. Fora que vc pode ter várias classs filhas de Carro que tb precisam de validação.

[quote]
No caso que eu exemplifiquei acima, complexidade de uma validação não tem como fugir da classe que o estado, está sendo validado.[/quote]

Claro. Obviamente o validador valida alguma coisa. A questão é que a ‘coisa’ não se pode validar a si mesma.
Imagina que é um objeto Pessoa e vc quer validar se ela tem cadastro criminal. Vc confiaria em perguntar à própria pessoa se ela tem esse cadastro , ou vc recorreria às autoridades para verificar isso ?

interface Validator<T> {

   ValidationResult validate (T obj );

}

com esta interface você pode validar qq objeto com qualquer logica. Vc pode compor vários validadores para obter um resultado e vc pode aplicar o mesmo validador a objetos de classes diferentes desde que partilhem alguma interface em comum ( mesmo que por reflection). As possibilidades são infinitas. Já se em definir um simples isValid() no proprio objeto que quero validar não vou ter nenhuma dessas vantagens.

Enfim, basta consulta qq API de validação para ver que é assim que se faz. O que o Hibernate Validator faz é definir Annotations com os parametros dos Validadores de forma que o Validador fica implícito à anotação e usa reflection para validar o objeto.
Não é nada mais que isso. Não é nada que vc tb não possa fazer.

Acho importante ressaltar algumas coisas aqui.

Sim! Assim estaremos aplicando o padrão GRASP Especialista.

Acho legal pensar um pouco sobre esta frase aqui!

Quando um carro possui um amassado ele esta em estado ilegal?
Quando um carro não esta com o IPVA em dia ele esta em estado ilegal?

Ao meu ver as respostas para as questões é uma negativa, pois eu posso ter um carro todo amassado e que não esteja com o IPVA em dia. Então carro não deve verificar esse tipo de coisa.

Outra pergunta. O carro pode ter uma cor NULL?

A resposta é não um objeto carro com uma cor NULL não deve existir no sistema. Então carro deve fazer esse tipo de verificação para evitar existir no sistema um carro com cor NULL.

Agora que vai verificar o IPVA e se o carro possui amassados em vistoria?
Simples é o vistoriador. É o vistoriador que sabe os criterios de aprovação para uma vistoria.

Um exemplo de código


public class Carro {
	
	private boolean amassado;
	private boolean ipvaDia;
	private String cor;
	
	public Carro(boolean amassado, boolean ipvaDia, String cor) {

		if(cor == null)
			throw new IllegalArgumentException();
		
		this.cor = cor;
		this.amassado = amassado;
		this.ipvaDia = ipvaDia;	
	}

	public boolean isAmassado() {
		return amassado;
	}

	public boolean isIpvaDia() {
		return ipvaDia;
	}
	
}

public class CriteriosVistoria {

	 @AssertFalse 
	private boolean amassado;
	 @AssertTrue 
	private boolean ipvaDia;
		
	public CriteriosVistoria(Carro carro){
		amassado = carro.isAmassado();
		ipvaDia = carro.isIpvaDia();
	}
	
	public boolean passaNosCriterios(){
		ClassValidator<CriteriosVistoria> classValidator = new ClassValidator<CriteriosVistoria>(CriteriosVistoria.class);  
		invalidValues = classValidator.getInvalidValues(this);  
		
		if(invalidValues == null)
			return true;
		
		if((invalidValues != null) && (invalidValues.length == 0))
			return true;
		  
		return false;
	}
}

public class Vistoriador {

	public boolean estaAprovado(Carro carro){
		CriteriosVistoria criteriosVistoria;
		
		criteriosVistoria = new CriteriosVistoria(carro);
		
		return criteriosVistoria.passaNosCriterios();		
	}
}


Essa é a validação de que eu estou falando. :smiley:
A validação que eu acredito que deva ficar em um método de validação na própria classe, é essa que o brunohansen explicou.

Essa é a validação de que eu estou falando. :smiley:
A validação que eu acredito que deva ficar em um método de validação na própria classe, é essa que o brunohansen explicou.[/quote]

Então deixem-me dizer que isso não se chama validação, chama-se consistência.
O objeto é responsável pela sua consistência (a consistência do seu estado, mais propriamente) mas ele não é responsável pela sua validação (que no exemplo do brunohansen é o que o Vistoriador faz)

Ok. Acho que o Sergio matou minha dúvida.

O fato do Carro estar com ipvaPago=false e amassado=true não torna o estado do objeto inconsistente, pois ambos são dimensões válidas para o objeto.
É apenas uma regra de negócio. Validação.
Agora se ipvaPago recebesse um valor “Mais ou menos pago” aí sim estaria com seu estado inválido porque está fora da invariante. Ou está pago ou não está. Neste caso o próprio objeto Carro é responsável por manter tal estado consistente.

Se alguém ainda não leu: http://fragmental.com.br/wiki/index.php/Contratos_Nulos vale muito a pena.

Alguém discorda?

Muuuuuuuuuuuuito obrigado!!! :smiley:
Estou procurando esta palavra desde minha primeira resposta mas não lembrava. :shock:
É isso mesmo que eu queria ter dito, mas não lembrava da droga da palavra: Consistência.

:idea:
Ah! Por favor, em (quase) todos os lugares que eu falo validação(e suas variações) eu queria dizer consistência.

Mais uma vez obrigado.