Saltar al contenido principal

Chain of responsibility

BehavioralGang of FourDecouplingAlrededor de 3 min

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

alt text
Diagrama de clases de la cadena de responsabilidad

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

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

Créditos