Hola Gente
Esta linda la charla, un comentario.
Yo las validaciones, y creo que es un termino muy amplio, las hago mediante el uso de specifications.
Siempre que tengo que verificar una regla de negocio implemento las specifications necesarias.
Codigo mas claro, siempre se destaca el valor conceptual en terminos de negocio.
Se reduce sobre manera la cantidad de if que se utiliza.
Ej.:
Specification<Boleto> claseNoConsulta=SpecificationFactory.GetInstance().getBoletoClaseNoConsulta();
Specification<Boleto> sectorNoConsulta=SpecificationFactory.GetInstance().getBoletoSectorNoConsulta();
Specification<Boleto> apoderadoConsulta=SpecificationFactory.GetInstance().getBoletoConApoderado();
Specification<Boleto> cuitNoConsulta=SpecificationFactory.GetInstance().getBoletoCUITNoConsulta();
Specification<Boleto> conceptoVinculanteConsulta=SpecificationFactory.GetInstance().getBoletoVinculante();
// Ninguna especificación es inválida según los valores de creación de b
// El resultado debe ser false !!!!
BoletoParaTest b=new BoletoParaTest(false,843,10,619,"33123456783");
bool result=claseNoConsulta.or(sectorNoConsulta)
.or(apoderadoConsulta)
.or(cuitNoConsulta)
.or(conceptoVinculanteConsulta)
.isSatisfiedBy(b);
Assert.IsFalse(result,"Se esperaba falso para esta especificación compuesta.");
// La especificación apoderadoSpec es inválida segun los valores de creación de b
// El resultado debe ser true !!!!
b=new BoletoParaTest(true,843,10,619,"33123456783");
result=claseNoConsulta
.or(sectorNoConsulta)
.or(apoderadoConsulta)
.or(cuitNoConsulta)
.or(conceptoVinculanteConsulta)
.isSatisfiedBy(b);
Assert.IsTrue(result,"Se esperaba verdadero para esta especificación compuesta, boleto con apoderado).");
// La especificación concepto es inválida segun los valores de creación de b
// El resultado debe ser true !!!!
b=new BoletoParaTest(false,847,10,619,"33123456783");
result=claseNoConsulta
.or(sectorNoConsulta)
.or(apoderadoConsulta)
.or(cuitNoConsulta)
.or(conceptoVinculanteConsulta)
.isSatisfiedBy(b);
Assert.IsTrue(result,"Se esperaba verdadero para esta especificación compuesta, boleto con concepto vinculante).");
Un ejemplo mas terrenal, un negocio que seguro conoce todo el mundo, relaciones familiares.
Es un ejemplito que uso siempre, se quiere determinar si person a es hemana de b y sobrina de d...
o cualquier relacion parecida:
static void Main(string[] args)
{
Persona p1 = new Persona { Nombre = "Elvira", Apellido = "Bonffantino" };
Persona p2 = new Persona { Nombre = "Fernando", Apellido = "Calvin" };
Persona p3 = new Persona { Nombre = "Daniel", Apellido = "Calvin", Madre = p1, Padre = p2,
Nacimiento = new DateTime(1962, 05, 05) };
Persona p4 = new Persona { Nombre = "Fernanda", Apellido = "Calvin", Madre = p1, Padre = p2 };
Persona p5 = new Persona { Nombre = "Silvia", Apellido = "Calvin", Madre = p1, Padre = p2 };
Persona p6 = new Persona { Nombre = "Carlos", Apellido = "Moruzzi" };
Persona p7= new Persona { Nombre = "Florencia", Apellido = "Moruzzi", Madre = p5, Padre = p6 };
Persona p8 = new Persona { Nombre = "Bruno", Apellido = "Moruzzi", Madre = p5, Padre = p6 };
Persona p9 = new Persona { Nombre = "Gerardo", Apellido = "Calvin",Padre=p3, Nacimiento =new DateTime(1989,09,12) };
Persona p10 = new Persona { Nombre = "Matias", Apellido = "Calvin", Padre = p3, Nacimiento = new DateTime(2003, 04, 02) };
// Probando una especificacion simple
Console.WriteLine("{0} es adulto? {1}", p9, new EsAdulto().isSatisfiedBy(p9));
Console.WriteLine("{0} es adulto? {1}", p10, new EsAdulto().isSatisfiedBy(p10));
// Probando algo mas complejo
Console.WriteLine("{0} es adulto e hijo de {1}? {2}", p9,p5 ,new EsAdulto().and(new EsHijoDe(p5)).isSatisfiedBy(p9) );
Console.WriteLine("{0} es adulto e hijo de {1}? {2}", p9, p3, new EsAdulto().and(new EsHijoDe(p3)).isSatisfiedBy(p9));
Console.WriteLine("{0} es hijo de {1}? {2}", p8, p7, new EsHijoDe(p7).isSatisfiedBy(p8));
Console.WriteLine("{0} es hijo de {1}? {2}", p3, p2, new EsHijoDe(p2).isSatisfiedBy(p3));
Console.WriteLine("{0} y {1}, son hermanos? {2}", p4, p8, new EsHermanoDe(p8).isSatisfiedBy(p4));
Console.WriteLine("{0} y {1}, son hermanos? {2}", p3, p3, new EsHermanoDe(p3).isSatisfiedBy(p3));
Console.WriteLine("{0} y {1}, son hermanos? {2}", p4, p5, new EsHermanoDe(p5).isSatisfiedBy(p4));
Console.WriteLine("{0} es tío de {1}? {2}", p4, p5, new EsTioDe(p4).isSatisfiedBy(p5));
Console.WriteLine("{0} es tío de {1}? {2}", p4, p7, new EsTioDe(p7).isSatisfiedBy(p4));
}
Esas specifications estan compuestas a su vez por otras specifications, por ejemplo:
class EsHijoDe:PersonaSpecification
{
public EsHijoDe(Persona perona)
{
base.persona = perona;
}
public override bool isSatisfiedBy(Persona t)
{
return(t.Padre==persona || t.Madre==persona);
}
}
public class EsHermanoDe:PersonaSpecification
{
public EsHermanoDe(Persona persona)
{
base.persona = persona;
}
public override bool isSatisfiedBy(Persona t)
{
return new EsHijoDe(t.Madre).
or(new EsHijoDe(t.Padre)).
isSatisfiedBy(persona) && t!=persona;
}
}
Espero que no les moleste, adjunto el ejemplito completo en c#
Un problema que se presentaba con esta implementación de specificatiosn es que si alguna no se cumple no podía saber cual era la que no se cumplia, para eso agregue una sobre carga al metodo is satisfiedby agrgegando como parametro una lista instanciad previamente, el metodo isstisfiedby si la la lista no es nula y la especificación no se cumple agrega un item a la lista, al ejecutar una specificacion compleja, como EsHermano, si la misma no se cumple tendría una linea agregada donde incicaria que ella no se cumplio y a su vez si otra de las invocadas no se cumple, por ejemplo EsHijoDe, se agregaria tambien.
Ej, ( este es en java )
@Service("EsDiaHabilSpecification")
@Scope("prototype")
public class EsDiaHabilSpecification
extends SpecificationDomain<Date>
{
@Override
public boolean isSatisfiedBy(Date t, List<String> notSatified) {
boolean result= DateHelper.esDiaHabil(t);
super.addNotSatisfied(result, 1044 ,String.format("Fecha: %1$s", t.toString() ),notSatified);
return result;
}
}
Otro, compuesto..
public class ContraAsientoValidoSpecification
extends SpecificationDomain<SolicitudArbitraje>
{
@Override
public boolean isSatisfiedBy(SolicitudArbitraje t, List<String> notSatified) {
boolean result = true;
ProcesoBatchActivoSpecification porocesoBatchActivo = new ProcesoBatchActivoSpecification();
//Validar el estado de la solicitud (5)
result = t.getEstado() == EstadoDeSolicitud.AUTORIZADA_OP_PENDIENTE;
result = porocesoBatchActivo.isSatisfiedBy(null, notSatified) && result;
super.addNotSatisfied(result,1732,String.format("Estado de solicitud incorrecto para contra asentar: %1$s", t.getEstado().toString()),notSatified);
return result;
}
En java, utilizo mucho spring data, le paso specifications a los repositorios y en base a ellas se arman dinamicamente los criterias para armar queries desde JPA. ( por debajo hibernate )
Esta muy bueno, el codigo es limpio, el lenguaje del negocio, del dominio esta muy presente en el código.
Se lee con facilidad, no solo por las entidades, las reglas de negocio se expresan en el lenguaje del dominio.
Estaría bueno conocer sus opiniones o experiencias....
Daniel