Introducción
Continuo aquí la serie «Patterns» que comencé hace algún tiempo. Esta serie se compone de las entradas anteriores:
- [Patterns] Distributed Applications using FactoryMethod and ServiceLocator Patterns
- [Patterns] Distributed Applications using Facade Pattern
- [Patterns] Lifetime Container Pattern
- [Patterns] UnitOfWork Pattern
Antes de comenzar les dejo dejo esta imagen:
Extraido de http://www.vincehuston.org/dp/
Volviendo al tema, ahora nos toca el «Chain Of Responsibilities Pattern». Este es un patrón muy sencillo y muy usado. Chain Of Responsibilities permite desacoplar al objeto que realiza una petición de aquellos quienes pueden tratarla. Es decir, cuando un objeto necesita que se realice alguna acción como, por ejemplo, aprobar una rendición de viaticos, el objeto solicitante (el que requiere que se apruebe la solicitud) no necesita conocer la mecánica de las aprobaciones de viaticos sino que solo necesita invocar a la cadena de responsabilidades y solicitarle que maneje la aprobación solicitada. En la cadena pueden existir múltiples objetos responsables de aprobar o rechazar las rendiciones de viaticos según ciertos criterios e incluso es posible agregar nuevos responsables y/o quitar alguno(s) existente(s).
Veamos el diagrama de clases de este patrón:
Como puede verse, existe una clase abstracta «Handler» (puede implamantarse también mediante una interface «IHandler» ya que practicamente no contiene códido) la cual mantiene una referencia a otro Handler por medio de la propiedad «Next». Así, es claro que estamos ante una «lista enlazada» de Handlers. Luego, encontramos ‘n’ implementaciones concretas de Handler (o IHandler) como ConcreteHandler1 y ConcreteHandler2 las que sobreescriben el método «HandlerRequest()» y por lo tanto, en cada eslabón de la cadena encontraremos distintos handlers que manejarán cada uno una petición según se cumpla alguna condición específica.
En el ejemplo que mencionaba más arriba, y acotando el ejemplo a nuestra industria, podremos encontrar que la condición para la aprobación de una rendición de viaticos podría tener relación con el monto total rendido. Y podríamos imaginar el siguiente procedimiento:
- Si el monto total rendido es menor a $1000, lo aprobará el Project Leader,
- Si el monto total rendido es mayor o igual a $1000 y menor a $5000, lo aprobará el Program Manager y,
- Si el monto total rendido es mayor o igual a $5000, lo deberá aprobar el Director del Centro.
En este caso, la cadena de responsabilidades para la aprobación de viaticos se compondrá de 3 responsables: una instancia de ProjectLeader, una de ProgramManager y una de Director.
El objeto solicitante deberá conocer al primer responsable (después veremos como mejorar esta situación) y solicitarle la aprobación como sigue:
public void ToApprove(Accountability acc) { ProjectLeader.HandleRequest(acc); }
public class ProjectLeader : Handler { public override void HandleRequest(Accountability request) { if (request.Sum < 1000) System.Console.WriteLine("Aprobado por el Project Leader"); else base.HandleRequest(request); } } public class ProgramManager : Handler { public override void HandleRequest(Accountability request) { if (request.Sum >= 1000 && request.Sum < 5000) System.Console.WriteLine("Aprobado por el Program Manager"); else base.HandleRequest(request); } } public class Director : Handler { public override void HandleRequest(Accountability request) { if (request.Sum >= 5000) System.Console.WriteLine("Aprobado por el Director"); else base.HandleRequest(request); } }
Sistema de plug-in
Mejora
El que el solicitante deba conocer al primer Handler es algo poco elegante, por eso la propuesta consiste en crear una clase ChainOfResponsibilities que se encargue de mantener una referencia al primer Handler como así también, de las tareas de dar el Kick off de las peticiones para que comience a recorrer la cadena como de agregar/quitar Handlers de/a la cadena.
En el ejemplo de abajo puede verse esta clase con las propiedades FirstHandler y los métodos Add y StartRequest. La propiedad LastHandler es privada y mantiene una referencia al último elemento de la cadena para hacer más rápido el agregado de nuevos Handlers ya que de esta manera nos evitamos el tener que recorrer la cadena para llegar al final.
Cuando se implementa como pipeline, puede que una petición haya sido tratada por uno más handlers hasta ue uno falla. En este caso muchas veces puede (y muchas no) implementarse un mecanizmo de Rollback implementando un método Reverse en cada handler de manera que recorriendo la cadena en sentido contrario pueda volverse la petición a su estado original. Esto no es posible mediante una lista enlazada por lo que existe una alternativa.
Alternativas a la lista enlazada
Si bien este patrón se ideó como una lista enlazada, es posible implementarlo por medio de una lista de Handlers como List<IHandler> solo que en este caso, los handlers no conocerán a su sucesor sio que deberán solicitarle a la clase contenedora que pase la petición al próximo manejador.
Ejemplo
Para ejemplificar un poco más este patrón he creado un pequeño proyecto en el cual se implementa como una pipeline (el resultado de uno pasa al siguiente) para un sistema de Mobile Banking en el cual un cliente de un banco que cuenta con este servicio quiere enviarle dinero por medio de su celular a otra persona de quien conoce también su número de celular (movil).
En este caso, cuando llega una solicitud de envio de dinero, el sistema la pasa por una pipeline la que realiza las siguientes comprobaciones/acciones:
- Verifica que exista un cliente registrado en el banco con ese número de celular. Si esto falla es porque alguien que no es cliente está haciendo peticiones al sistema.
- Verifica que el cliente no haya sobrepasado su límite anual de transacciones. Anualmente cada cliente no puede hacer transferencias por más de un monto límite determinado para él.
- Verifica que el cliente no haya transferido o intente transferir más dinero del que se le estableció como límite mensual y,
- si todo lo anterior es correcto, realiza la transacción. Esta podría ser muy compleja.
El diagrama de clases para este ejemplo es el siguiente:
La pipeline
Notese que pueden agregarse y/o quitarse operaciones a esta pipeline.
Código de ejemplo ChainOfResponsibilitiesPatternExample.zip.
El código no es de un sistema real y es extremadamente sencillo. Lo hice solo para mostrar un poco de código y nada más que para eso.
Referencias y otros ejemplo
- Wikipedia: Chain of Responsibility (patrón de diseño)
- Vice Huston: Design Patterns
- The Chain of Responsibility pattern’s pitfalls and improvements
- Follow the Chain of Responsibility
- Pattern Summaries: Chain of Responsibility
- Chain of Responsibility (básico)
- Chain of Responsibility (excelente)
Continuará….
Lucas Ontivero