Veamos las opciones, donde podemos colocar el código de validación:
- Dentro de los setters en constructor.
- Dentro del
build()método.
- Dentro de la entidad construida: se invocará en el
build()método cuando se cree la entidad.
La opción 1 nos permite detectar problemas antes, pero puede haber casos complicados en los que podemos validar la entrada solo teniendo el contexto completo, por lo tanto, haciendo al menos parte de la validación en el build()método. Por lo tanto, elegir la opción 1 conducirá a un código inconsistente con parte de la validación realizada en un lugar y otra parte realizada en otro lugar.
La opción 2 no es significativamente peor que la opción 1, porque, por lo general, los configuradores en el generador se invocan justo antes que build(), especialmente, en las interfaces fluidas. Por lo tanto, todavía es posible detectar un problema lo suficientemente temprano en la mayoría de los casos. Sin embargo, si el generador no es la única forma de crear un objeto, dará lugar a la duplicación del código de validación, ya que deberá tenerlo en todas partes donde cree un objeto. La solución más lógica en este caso será colocar la validación lo más cerca posible del objeto creado, es decir, dentro de él. Y esta es la opción 3 .
Desde el punto de vista SÓLIDO, poner la validación en el generador también viola SRP: la clase del generador ya tiene la responsabilidad de agregar los datos para construir un objeto. La validación es establecer contratos en su propio estado interno, es una nueva responsabilidad verificar el estado de otro objeto.
Por lo tanto, desde mi punto de vista, no solo es mejor fallar tarde desde la perspectiva del diseño, sino que también es mejor fallar dentro de la entidad construida, en lugar de hacerlo en el propio constructor.
UPD: este comentario me recordó una posibilidad más, cuando la validación dentro del generador (opción 1 o 2) tiene sentido. Tiene sentido si el constructor tiene sus propios contratos en los objetos que está creando. Por ejemplo, suponga que tenemos un generador que construye una cadena con contenido específico, por ejemplo, una lista de rangos de números 1-2,3-4,5-6. Este constructor puede tener un método como addRange(int min, int max). La cadena resultante no sabe nada sobre estos números, ni debería tener que saberlo. El propio constructor define el formato de la cadena y las restricciones en los números. Por lo tanto, el método addRange(int,int)debe validar los números de entrada y lanzar una excepción si max es menor que min.
Dicho esto, la regla general será validar solo los contratos definidos por el propio constructor.
nullobjeto cuando haya un problemabuild().