Vamos ver as opções, onde podemos colocar o código de validação:
- Dentro dos setters no construtor.
- Dentro do método
build()
. - Dentro da entidade construída: ela será invocada no método
build()
quando a entidade estiver sendo criada.
A opção 1 nos permite detectar problemas mais cedo, mas pode haver casos complicados quando podemos validar a entrada tendo apenas o contexto completo, fazendo assim pelo menos parte da validação no método build()
. Assim, a escolha da opção 1 resultará em código inconsistente, com parte da validação sendo feita em um local e outra sendo feita em outro local.
A opção 2 não é significativamente pior que a opção 1, porque, geralmente, os configuradores no construtor são invocados imediatamente antes do build()
, especialmente em interfaces fluentes. Assim, ainda é possível detectar um problema com antecedência suficiente na maioria dos casos. No entanto, se o construtor não for a única maneira de criar um objeto, isso levará à duplicação do código de validação, porque você precisará tê-lo em todos os lugares em que criar um objeto. A solução mais lógica, neste caso, será colocar a validação o mais próximo possível do objeto criado, ou seja, dentro dele. E esta é a opção 3 .
Do ponto de vista do SOLID, colocar a validação no construtor também viola o SRP: a classe do construtor já tem a responsabilidade de agregar os dados para construir um objeto. A validação é estabelecer contratos em seu próprio estado interno, é uma nova responsabilidade verificar o estado de outro objeto.
Assim, do meu ponto de vista, não só é melhor falhar tarde na perspectiva do design, como também é melhor falhar dentro da entidade construída, e não no próprio construtor.
UPD: > este comentário me lembrou de mais uma possibilidade, quando a validação dentro do construtor (opção 1 ou 2) faz sentido. Faz sentido se o construtor tiver seus próprios contratos nos objetos que está criando. Por exemplo, suponha que tenhamos um construtor que construa uma cadeia com conteúdo específico, digamos, lista de intervalos de números 1-2,3-4,5-6
. Esse construtor pode ter um método como addRange(int min, int max)
. A string resultante não sabe nada sobre esses números, nem deveria saber. O próprio construtor define o formato da string e as restrições nos números. Assim, o método addRange(int,int)
deve validar os números de entrada e lançar uma exceção se max for menor que min.
Dito isso, a regra geral será validar apenas os contratos definidos pelo próprio construtor.