Como ya he dicho antes, el estar en contacto con equipos de desarrollo me resulta una experiencia muy valiosa.
Estábamos la semana pasada discutiendo sobre el mejor método para validar objetos del modelo de dominio. Mi primera respuesta ante esa pregunta es: nunca los objetos del modelo debieran estar inválidos, en primer lugar.
Cada objeto de dominio debiera inicializarse completamente en su constructor, ya sea inicializando valores por defecto, ya sea recibiendo valores desde el inicializador.
Algunas veces alguien menciona que no aprecia los constructores con 20 parámetros a lo cual respondo con un cuestionamiento sobre si realmente ese objeto debe ser tan “grande” o si hay uno o mas objetos no descubiertos aun.
Para no hacer esta entrada demasiado larga, supongamos que acordamos que inicializamos completamente el objeto, con valores válidos en el constructor y que ningún método publico nos permite modificarlo dejándolo en estado invalido (1). En este caso, nunca es necesario validar nuestro objeto puesto que sabemos que, si esta instanciado, nunca puede ser inválido.
Y aquí llegamos a la práctica que recomendé a unos colegas la semana pasada: si no es trivial validar una serie de datos y, una vez validados, construir el objeto, entonces usen un Builder. Las responsabilidades del Builder son varias:
- Asegurarse de tener todos los datos necesarios para construir el objeto
- Proporcionar una interfaz amigable para la especificación de todos los datos
- Construir el objeto
A continuación ejemplo con “fluent API”:
public class FacturaBuilder {
private Cliente _cliente;
private DateTime _fecha;
public FacturaBuilder ConCliente(Cliente cliente) {
_cliente = cliente;
return this;
}
public FacturaBuilder ConFecha(DateTime fecha) {
_fecha = fecha;
return this;
}
public Factura Build() {
AssertDatosSuficientesYCorrectos();
return new Factura(_cliente, _fecha);
}
private void AssertDatosSuficientesYCorrectos() {
if (_cliente == null)
throw new ArgumentNullException("cliente");
if (_fecha < DateTime.Today)
throw new ArgumentOutOfRangeException("fecha");
}
}
Y este código se usaría de esta manera:
var factura = new FacturaBuilder().
ConCliente(jose).
ConFecha(DateTime.Today).
Build();
Nos vemos!
Interesante…
Pero para el ejemplo que muestras, no sería mas sencillo, que el constructor recibiera el cliente y la fecha y que en el mismo invocara al método que realiza las validaciones?
Que pasa cuando una vez creado el objeto se requiere modificar alguna propiedad?
Hola Gerardo,
Si los parámetros del constructor son pocos y es trivial entender como se construye el objeto, entonces se justifica menos el uso de un Builder.
Yo lo uso solo en los pocos casos en que necesito “ayuda” para armar el objeto con una sintaxis mas agradable (los metos ConXXX() en este caso).
Otro caso, más especializado, es el de un asistente en el que recolectas los elementos necesarios en varios pasos para crear el objeto al final con todo lo necesario.
Un abrazo
Totalmente de acuerdo con que nunca los objetos del modelo debieran estar inválidos, en primer lugar… Cuando se dan escenarios donde eso no pasa, en mi experiencia el 99% de las veces es porque falta pensar un poco más el diseño. Insólitamente el tema sigue siendo controversial.
Cuando el constructor tiene muchos parámetros seguramente hay que pensar en refactorizar algo (“Introduce parameter object”: ejemplo trivial, se quiere crear algo pasando tres números para día, mes, año en lugar de algún objeto fecha) antes de aplicar el builder.
El uso de la fluent API le da un plus importante en cuanto a legibilidad.
Saludos