Cadena de responsabilidad
El patrón de diseño cadena de responsabilidad es un patrón de comportamiento que evita acoplar el emisor de una petición a su receptor dando a más de un objeto la posibilidad de responder a una petición. Para ello, se encadenan los receptores y pasa la petición a través de la cadena hasta que es procesada por algún objeto. Este patrón es utilizado a menudo en el contexto de las interfaces gráficas de usuario donde un objeto puede estar compuesto de varios objetos (que generalmente heredan de una super clase "vista"). No se debe confundir con el patrón Composite (patrón de diseño) que se basa en un concepto similar. Según si el ambiente de ventanas genera eventos, los objetos los manejan o los "pasan" (transmiten) hasta que algún elemento consume dicho evento y se detiene la propagación. Un claro ejemplo de cadena de responsabilidades sería una estructura de datos que contiene nodos cuya implementación es opaca para el usuario de dicha estructura de datos (no conoce salvo la interfaz de la estructura de datos). Internamente los nodos se relacionan mediante punteros y una llamada a un método de la estructura de datos hará que un mensaje se propague por la cadena de nodos hasta llegar a su receptor. Es decir, una petición de "búsqueda(clave)" al contenedor de nodos hará que el mismo pase dicho mensaje a uno de los nodos (raíz, primero, etc; dependiendo de como se haya implementado la estructura de datos) devolviendo el nodo el resultado si su atributo clave coincide con la búsqueda o mandando dicho mensaje al siguiente nodo en caso contrario. Obviamente se daría este caso a niveles teóricos pues existen formas más óptimas de solventar el anterior problema de ejemplo y; sobre todo, ha de tenerse en cuenta que una cadena muy grande de muchos elementos puede llegar a apilar en memoria una gran cantidad de llamadas a procedimientos hasta que la cadena retorne un resultado con su consiguiente coste asociado.
Motivación
[editar]Supongamos un servicio de ayuda sensible al contexto para una interfaz gráfica. El usuario puede obtener ayuda en cualquier parte de la interfaz pulsando con el ratón sobre ella. La ayuda proporcionada depende de la parte de la interfaz que se haya seleccionado así como de su contexto. Si no existe información de ayuda específica para esa parte el sistema debería mostrar un mensaje de ayuda más general sobre el contexto inmediato. El problema es que el objeto que en última instancia proporciona la ayuda no conoce explícitamente al objeto (por ejemplo, el botón) que inicializa la petición. Necesitamos un modo de desacoplar el botón que da lugar a la petición de ayuda de los objetos que podrían proporcionar dicha información. El patrón Cadena de Responsabilidad define cómo hacerlo. La idea de este patrón es desacoplar a los emisores y a los receptores dándole a varios objetos la posibilidad de tratar una petición. La petición se pasa a través de una cadena de objetos hasta que es procesada por uno de ellos.
Supongamos que el usuario solicita ayuda sobre un botón denominado "Imprimir", el cual se encuentra en una instancia de DialogoDeImpresion. El siguiente diagrama muestra cómo la petición de ayuda se reenvía
En este caso, la petición no es procesada ni por unBotonDeImpresion ni por unDialogoDeImpresion; se detiene en unaAplicación que podrá procesarla u obviarla. El cliente que dio origen a la petición no tiene ninguna referencia directa al objeto que finalmente la satisface. Para reenviar la petición a lo largo de la cadena, y garantizar que los receptores permanecen implícitos, cada objeto de la cadena comparte una interfaz común para procesar peticiones y para acceder a su sucesor. Por ejemplo, en este sistema de ayuda podría definirse una clase ManejadorDeAyuda.
Las clases Botón, Diálogo y Aplicación usan las operaciones de ManejadorDeAyuda para tratar peticiones de ayuda. La operación ManejarAyuda de ManejadorDeAyuda reenvía la petición al sucesor de manera predeterminada. Las subclases pueden redefinir esta operación para proporcionar ayuda en determinadas circunstancias; en caso contrario, pueden usar la implementación predeterminada para reenviar la petición.
Aplicabilidad
[editar]El patrón Cadena de Responsabilidad debe usarse cuando:
- hay más de un objeto que puede manejar una petición, y el manejador no se conoce a priori, sino que debería determinarse automáticamente.
- se quiere enviar una petición a un objeto entre varios sin especificar explícitamente el receptor.
- el conjunto de objetos que pueden tratar una petición debería ser especificado dinámicamente.
Estructura
[editar]Participantes
[editar]- Manejador: define una interfaz para tratar las peticiones. Opcionalmente, implementa el enlace al sucesor.
- ManejadorConcreto: trata las peticiones de las que es responsable; si el ManejadorConcreto puede manejar la petición, lo hace; en caso contrario la reenvía a su sucesor.
- Cliente: inicializa la petición a un Manejador Concreto de la cadena.
Colaboraciones
[editar]Cuando un cliente envía una petición, esta se propaga a través de la cadena hasta que un objeto ManejadorConcreto se hace responsable de procesarla.
Ventajas
[editar]Las ventajas de este patrón son:
- Reduce el acoplamiento. El patrón libera a un objeto de tener que saber qué otro objeto maneja una petición. Ni el receptor ni el emisor se conocen explícitamente entre ellos, y un objeto de la cadena tampoco tiene que conocer la estructura de esta. Por lo tanto, simplifica las interconexiones entre objetos. En vez de que los objetos mantengan referencias a todos los posibles receptores, sólo tienen una única referencia a su sucesor.
- Añade flexibilidad para asignar responsabilidades a objetos. Se pueden añadir o cambiar responsabilidades entre objetos para tratar una petición modificando la cadena de ejecución en tiempo de ejecución. Esto se puede combinar con la herencia para especializar los manejadores estáticamente.
Por otra parte presenta el inconveniente de no garantizar la recepción. Dado que las peticiones no tienen un receptor explícito, no hay garantías de que sean manejadas. La petición puede alcanzar el final de la cadena sin haber sido procesada.
Implementación
[editar]- Implementación de la cadena sucesora. Hay dos formas posibles de implementarla:
- Definir nuevos enlaces (normalmente en el Manejador, pero también podría ser en los objetos ManejadorConcreto).
- Usar enlaces existentes (otras asociaciones existentes). Por ejemplo, en el patrón Composición puede existir ya que un enlace al padre puede utilizarse para definir la cadena de responsabilidad sin necesidad de añadir otra asociación.
- Conexión de los sucesores. Si no hay referencias preexistentes para definir una cadena, entonces tendremos que introducirlas nosotros mismos. En este caso, el Manejador define la interfaz y además, se encarga de mantener el sucesor. Esto permite que el manejador proporcione una implementación predeterminada de ManejarPetición que reenvíe la petición al sucesor (si hay alguno). Si una subclase de ManejadorConcreto no está interesada en dicha petición, no tiene que redefinir la operación de reenvío.
- Representación de peticiones. Hay varias opciones para representar las peticiones:
- Una petición es una invocación a una operación insertada en el código. Esto resulta conveniente y seguro, pero solo se pueden reenviar el conjunto prefijado de peticiones que define la clase Manejador.
- Una única función manejadora que reciba un código de petición como parámetro. Esto permite un número arbitrario de peticiones pero emisor y receptor deben ponerse de acuerdo sobre cómo codificarse la petición.
Patrones relacionados
[editar]Este patrón se aplica en ocasiones con el patrón Composición. En él, los padres de los componentes pueden actuar como sucesores.
Ejemplo
[editar]Diagrama de clases
[editar]Diagrama de clases comentado
[editar]Java
[editar]public class Cliente {
public static void main(String argv[]) {
Unidad smith = new Coronel("Smith", null);
Unidad truman = new Coronel("Truman", "Tomar posición enemiga");
Unidad ryan = new Soldado("Ryan");
Unidad rambo = new Soldado("Rambo");
System.out.println(rambo.orden()); // rambo ->
rambo.establecerMando(truman);
System.out.println(rambo.orden()); // rambo -> truman
ryan.establecerMando(rambo);
System.out.println(ryan.orden()); // ryan -> rambo -> truman
}
}
/**
* La clase Unidad representa la clase abstracta manejadora de la cadena de responsabilidad.
* El servicio delegado en la cadena es la solicitud de una orden al mando directo
*/
public abstract class Unidad {
/* en el constructor, además de un nombre para la unidad, se inicializa la referencia
que implementa la cadena de responsabilidad (_mando): en principio no hay sucesor */
public Unidad(String nombre) {
_mando = null;
_nombre = nombre;
}
public String toString() { return _nombre; }
// cambia el mando de una unidad (modifica cadena de responsabilidad)
public void establecerMando(Unidad mando) { _mando = mando; }
/* comportamiento por defecto de la cadena: delegar en el mando directo o, si se
alcanza el final de la cadena, utilizar una resolución por defecto (sin orden) */
public String orden() {
return (_mando != null ? _mando.orden() : "(sin orden)");
}
private Unidad _mando;
private String _nombre;
}
/**
* La clase Coronel modifica ligeramente el comportamiento por defecto de la cadena de
* responsabilidad: si el coronel tiene una orden específica, utiliza ésta para resolver
* el servicio. Si no tiene una orden específica (_orden==null), emplea el comportamiento
* convencional de las unidades
*/
public class Coronel extends Unidad {
// inicializa la parte de unidad e inicializa el estado propio del Coronel (_orden)
public Coronel(String nombre, String orden) {
super(nombre);
_orden = orden;
}
/* refinamiento del servicio que utiliza la cadena de responsabilidad, resolviendo
localmente si tiene órdenes específicas o comportándose convencionalmente en
caso contrario */
public String orden() { return (_orden != null ? _orden : super.orden()); }
public String toString() { return ("Coronel " + super.toString()); }
private String _orden;
}
/**
* Esta clase es una extensión instanciable de la superclase Unidad que respeta el
* comportamiento por defecto de la cadena de responsabilidad
*/
public class Soldado extends Unidad {
// el constructor sólo tiene que inicializar la parte correspondiente a la superclase
public Soldado(String nombre) {
super(nombre);
}
public String toString() { return ("Soldado " + super.toString()); }
}