[Patterns] Chain Of Responsibilites Pattern

Introducción

Continuo aquí la serie «Patterns» que comencé hace algún tiempo. Esta serie se compone de las entradas anteriores:

Antes de comenzar les dejo dejo esta imagen:
TablaDeElementos
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:

CoR
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);
}
En este caso, imaginemos que se rindieron $1700. Entonces, el primer handler, que es una instancia de ProjectLeader, evauará la condición «if (request.Sum < 1000)» para ver si es él quien debe manejar esta petición pero como el monto lo supera ($1700) le pasará la responsabilidad al segundo en la cadena, al Program Manager. El ProgramManager si es el responsable y por lo tanto se encargará de aprobar la solicitud y allí terminará la cadena. Es decir, el Director no intervendrá.
Abajo muestro un pequeño fragmento de código de estos tres Handlers:
 
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);
    }
}
En el ejemplo que acabo de dar es muy importante observar que los resposables deben estar ordenados en la cadena de menor a mayor porque si el Director fuese el primero, entonces todas las aprobaciones serian resueltas por él.  También hay que notar que aquí solo existe un responsable por cada tipo (según el monto) de rendiciones y que apenas uno la maneja, corta la cadena. Pero esto no tiene por que ser así, incluso una petición bien podría ir pasando por todos los elementos de la cadena de responsabilidades y que cada responsable se encargase de hacer algo con ella. Para los amantes del ObjectBuilder, su clase BuilderStrategyChain es una cadena de responsabilidades en la cual se alojan implementaciones concretas de IBuilderStaregy (ej: MethodExecutionStrategy y PropertySetterStrategy ) las que se encargan de realizar distintas acciones sobre los objetos como inyectarles valores a sus propiedades y ejecutarles ciertos métodos.
 
Otro aspecto relevante, aparte del orden de los handlers, es que el tratamiento de una solicitud no está garantizada. Una solicitud puede finalizar el recorrido de la cadena sin que ningún Handler la trate. Salvo en casos como en donde existe como último elemento de la cadena un Handler «Trata todo» el que provea de un tratamiento genérico por defecto para todas aquellas peticiones no manejadas por ninguno de los anteriores. Por ejemplo, podría existir un Handler del tipo LogguerHandler que escribiera una entrada en el event log diciendo que tal petición no fue manejada. 
 

Sistema de plug-in

La cadena de responsabilidades nos permite crear sistemas que soporten plugins de la siguiente manera: imaginemos que tenemos una aplicación que internamente mantiene información en un XmlDocument  que deseamos guardarlo en el disco bajo cierto nombre y extensión, como por ejemplo, archivo1.xml o informacion.doc. Es claro que según la extensión del archivo deberemos invocar a una clase que se encargue de las transformaciones necesaria, pero nosotros queremos dejar abierta la posibilidad de extender esto a otros tipos de archivos como hojas de calculo de MS Excel, PDF, JPG, TXT, etc. Queremos que cualquier desarrollador pueda desarrollar y extender esta característica de nuestro sistema mediante la registración de un plugin. La estrategia para resolver esto es mantener una CoR en la que cada Handler se encargue de manejar un tipo de archivo específico. La solicitud tendrá el XmlDocument, el nombre y la extensión del archivo y al ir recorriendo la cadena, cada handle preguntará si la extensión del archivo es la que él puede tratar, y de ser así, hará las transformaciones necesarias y lo guardará con el formato propio que él maneja. Como último eslabón puede registrarse un Handler que notifique al usuario de que ese tipo de archivo no puede ser manejado por el sistema.  

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: 

CoRImpl

 

La pipeline 

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

 

Continuará….

Lucas Ontivero

Sin categoría

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *