¡Hola!
La semana pasada en Kinetica celebramos nuevamente un Code&Beer. En esta oportunidad quisimos probar un Coding Dojo al estilo Randori.
La actividad consistía en agregar funcionalidad a un código sencillo existente. Este contaba con varios tests sobre la funcionalidad que ya había y se pretendía agregar muchos más usando TDD.
La aplicación modelaba una aprobación de la orden de un ítem a través de una cadena jerárquica de empleados. Cada empleado tenía la chance de rechazar el pedido por algún motivo y si lo aprobaba era evaluado por el siguiente en la cadena jerárquica.
El código original era más o menos así (C#):
public void ProcesarCompra(Compra compra)
{
if (compra.Costo > 500)
{
compra.Estado = Compra.EstadoCompra.Rechazada;
compra.Razón = "Lean: El costo unitario es muy alto";
}
if (compra.Costo * compra.Cantidad > 10000)
{
compra.Estado = Compra.EstadoCompra.Rechazada;
compra.Razón = "Fer: Demasiado dinero";
}
// Más reglas...
}
Arrancamos con 2 personas, cuando pasaban 6 minutos yo daba una señal, una salía y otra ocupaba su lugar. Les fui agregando nuevo requerimientos de a uno para que notaran cómo se complicaba el diseño:
- Se contrata una persona en el nivel más bajo de la cadena: Luís. Luís no acepta compras de más de 15 unidades.
- Parece que el desempeño de Luís ha sido excepcional por lo que es promovido a CEO. Ahora es el primero en juzgar las compras.
- Luís nos recomienda –y, como es el CEO, lo obedecemos- que contratemos a Adriana. Ella acepta solo algunos pedidos, como máximo 3.
Los chicos se dieron cuenta rápido que no era bueno tener todo en el mismo método y crearon un modelo con personas y reglas. Cambiando el código a algo así:
Persona lean = new Persona(new CostoUnitarioMayorA500());
Persona fer = new Persona(new CostoMayorA1000());
Persona luis = new Persona(new CantidadMayorA15());
(...)
public void ProcesarCompra(Compra compra)
{
if (!lean.ProcesarCompra(compra)) return;
if (!fer.ProcesarCompra(compra)) return;
if (!luis.ProcesarCompra(compra)) return;
}
La implementación estaba bastante bien y me dejó con pocos motivos para cambiarlo por la implementación con el patrón Chain of responsability. Lo que yo tenía en mente era algo así:
public abstract class PersonaJerarquica
{
public PersonaJerarquica SiguienteEnMando { get; set; }
public abstract void ProcesarCompra(Compra compra);
}
public class Lean : PersonaJerarquica
{
public Lean(PersonaJerarquica subdito)
{
this.SiguienteEnMando = subdito;
}
public override void ProcesarCompra(Compra compra)
{
if (compra.Costo > 500)
{
compra.Estado = Compra.EstadoCompra.Rechazada;
compra.Razón = "Lean: El costo unitario es muy alto";
}
if (this.SiguienteEnMando != null)
{
this.SiguienteEnMando.ProcesarCompra(compra);
}
}
}
(...)
PersonaJerarquica lean = new Lean(new Fernando(new Luis(null)));
public void ProcesarCompra(Compra compra)
{
lean.ProcesarCompra(compra);
}
La clave de esta implementación es el SiguienteEnMando y la construcción de la cadena de mando. El SiguienteEnMando desentiende un ProcesarCompra de otro. Es decir, encapsula cada persona.
Pero para mi es clave que la construcción de la cadena sea simple. En el ejemplo yo iba agregando reglas para que entendiera que es una parte del sistema flexible. Esta implementación hace muy fácil cambiar el orden de ejecución de la cadena de mando. Incluso, si lo deseáramos podemos hacerlo por configuración.
Resumiendo ventajas de esta implementación con el patrón:
- Encapsula las reglas.
- Es muy flexible ante cambios en la cadena.
¡Saludos!
Excelente.
Aquí, posiblemente, se podría agregar un patrón adicional para salvar la condición que verifica si existe un siguiente elemento en la cadena y así el programador no necesitaría tener que colocar una y otra vez la misma condición.
Si usamos Null Pattern o alguno similar que, en vez de tener un Null al final de la cadena simplemente adicionemos un elemento que no haga nada, nos ahorraríamos este trabajo, y el mismo podría ser seteado por la clase base abstracta para que solo nos preocupemos de la lógica de cada elemento.
Pingback: Chain of responsability Design Pattern – Un paso más allá | Kinetica Solutions Blogs