Veamos las opciones, donde podemos colocar el código de validación:
- Dentro de los setters en el constructor.
- Dentro del método
build()
.
- Dentro de la entidad construida: se invocará en el método
build()
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 con el contexto completo, por lo tanto, realizar al menos parte de la validación en el método build()
. Por lo tanto, la elección de la opción 1 conducirá a un código inconsistente con parte de la validación que se realiza en un lugar y otra parte que se realiza en otro lugar.
La opción 2 no es significativamente peor que la opción 1 porque, generalmente, los configuradores en el constructor se invocan justo antes del build()
, especialmente en las interfaces fluidas. Por lo tanto, aún 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, porque 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 de SOLID, poner la validación en el constructor también viola el SRP: la clase de constructor 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 constructor (opción 1 o 2) tiene sentido. Tiene sentido si el constructor tiene sus propios contratos sobre los objetos que está creando. Por ejemplo, supongamos que tenemos un constructor que construye una cadena con contenido específico, por ejemplo, la 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 acerca de 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 el máximo es menor que el mínimo.
Dicho esto, la regla general será validar solo los contratos definidos por el propio constructor.