Chain of responsibility
También conocido como
- Chain of Command
- Chain of Objects
- Responsibility Chain
Propósito
Evita acoplar el emisor de una petición a su receptor dando a más de un objeto la oportunidad de gestionar la petición. Encadena los objetos receptores y pasa la solicitud a lo largo de la cadena hasta que un objeto la gestione.
Explicación
Ejemplo real
El Rey Orco da órdenes en voz alta a su ejército. El más cercano a reaccionar es el comandante, luego un oficial y después un soldado. El comandante, el oficial y el soldado forman una cadena de responsabilidad.
En palabras sencillas
Ayuda a construir una cadena de objetos. Una solicitud entra por un extremo y sigue pasando de un objeto a otro hasta que encuentra un gestor adecuado.
Wikipedia dice
En diseño orientado a objetos, el patrón de cadena de responsabilidad es un patrón de diseño que consiste en una fuente de objetos de comando y una serie de objetos de procesamiento. Cada objeto de procesamiento contiene lógica que define los tipos de objetos de comando que puede manejar; el resto se pasa al siguiente objeto de procesamiento de la cadena.
Ejemplo programático
Traduciendo nuestro ejemplo con los orcos de arriba. Primero, tenemos la clase Request
:
import lombok.Getter;
@Getter
public class Request {
private final RequestType requestType;
private final String requestDescription;
private boolean handled;
public Request(final RequestType requestType, final String requestDescription) {
this.requestType = Objects.requireNonNull(requestType);
this.requestDescription = Objects.requireNonNull(requestDescription);
}
public void markHandled() {
this.handled = true;
}
@Override
public String toString() {
return getRequestDescription();
}
}
public enum RequestType {
DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX
}
A continuación, mostramos la jerarquía del gestor de peticiones.
public interface RequestHandler {
boolean canHandleRequest(Request req);
int getPriority();
void handle(Request req);
String name();
}
@Slf4j
public class OrcCommander implements RequestHandler {
@Override
public boolean canHandleRequest(Request req) {
return req.getRequestType() == RequestType.DEFEND_CASTLE;
}
@Override
public int getPriority() {
return 2;
}
@Override
public void handle(Request req) {
req.markHandled();
LOGGER.info("{} handling request \"{}\"", name(), req);
}
@Override
public String name() {
return "Orc commander";
}
}
// OrcOfficer and OrcSoldier are defined similarly as OrcCommander
El Rey Orco da las órdenes y forma la cadena.
public class OrcKing {
private List<RequestHandler> handlers;
public OrcKing() {
buildChain();
}
private void buildChain() {
handlers = Arrays.asList(new OrcCommander(), new OrcOfficer(), new OrcSoldier());
}
public void makeRequest(Request req) {
handlers
.stream()
.sorted(Comparator.comparing(RequestHandler::getPriority))
.filter(handler -> handler.canHandleRequest(req))
.findFirst()
.ifPresent(handler -> handler.handle(req));
}
}
La cadena de responsabilidad en acción.
var king=new OrcKing();
king.makeRequest(new Request(RequestType.DEFEND_CASTLE,"defend castle"));
king.makeRequest(new Request(RequestType.TORTURE_PRISONER,"torture prisoner"));
king.makeRequest(new Request(RequestType.COLLECT_TAX,"collect tax"));
La salida de la consola.
Orc commander handling request "defend castle"
Orc officer handling request "torture prisoner"
Orc soldier handling request "collect tax"
Diagrama de clases
Aplicabilidad
Utilice Cadena de Responsabilidad cuando
- Más de un objeto puede gestionar una petición, y el gestor no se conoce a priori. El gestor debe determinarse automáticamente.
- Se desea enviar una petición a uno de varios objetos sin especificar explícitamente el receptor.
- El conjunto de objetos que pueden gestionar una solicitud debe especificarse dinámicamente.
Usos conocidos
- Burbujeo de eventos en frameworks GUI donde un evento puede ser manejado en múltiples niveles de la jerarquía de un componente UI.
- Frameworks de middleware en los que una petición pasa a través de una cadena de objetos de procesamiento.
- Marcos de trabajo de registro donde los mensajes pueden pasar a través de una serie de registradores, cada uno posiblemente manejándolos de manera diferente.
- java.util.logging.Logger#log()
- Apache Commons Chain
- javax.servlet.Filter#doFilter()
Consecuencias
Ventajas:
- Acoplamiento reducido. El emisor de una petición no necesita conocer el manejador concreto que procesará la petición.
- Mayor flexibilidad a la hora de asignar responsabilidades a los objetos. Se pueden añadir o cambiar responsabilidades para gestionar una petición cambiando los miembros y el orden de la cadena.
- Permite establecer un gestor por defecto si no hay ningún gestor concreto que pueda gestionar la solicitud.
Desventajas:
- Puede ser difícil depurar y entender el flujo, especialmente si la cadena es larga y compleja.
- La petición puede quedar sin gestionar si la cadena no incluye un gestor "catch-all".
- Pueden surgir problemas de rendimiento debido a la posibilidad de pasar por varios gestores antes de encontrar el correcto, o no encontrarlo en absoluto.
Patrones Relacionados
- Comando: puede ser usado para encapsular una petición como un objeto, que puede ser pasado a lo largo de la cadena.
- Composite: la Cadena de Responsabilidad se aplica a menudo junto con el patrón Composite.
- Decorator: los decoradores pueden encadenarse de forma similar a las responsabilidades en el patrón Cadena de responsabilidad.