Vamos dar uma olhada nas opções, onde podemos colocar o código de validação:
- Dentro dos levantadores no construtor.
- Dentro do
build()
método.
- Dentro da entidade construída: será invocado no
build()
método quando a entidade estiver sendo criada.
A opção 1 nos permite detectar problemas mais cedo, mas pode haver casos complicados em que podemos validar a entrada apenas com o contexto completo, fazendo assim pelo menos parte da validação no build()
método. Assim, escolher a opção 1 levará a um código inconsistente, com parte da validação sendo feita em um local e outra parte sendo feita em outro local.
A opção 2 não é significativamente pior que a opção 1, porque, geralmente, os setters no construtor são chamados imediatamente antes 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 nesse 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 está estabelecendo 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 da perspectiva do design, mas também é melhor falhar dentro da entidade construída do que no próprio construtor.
UPD: esse comentário me lembrou 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 sequência com conteúdo específico, por exemplo, lista de intervalos de números 1-2,3-4,5-6
. Este construtor pode ter um método como addRange(int min, int max)
. A sequência resultante não sabe nada sobre esses números, nem precisa saber. O próprio construtor define o formato da sequência e as restrições nos números. Portanto, o método addRange(int,int)
deve validar os números de entrada e gerar uma exceção se max for menor que min.
Dito isto, a regra geral será validar apenas os contratos definidos pelo próprio construtor.