Wzorzec projektowy „Łańcuch odpowiedzialności”

Wzorzec projektowy łańcuch odpowiedzialności, jest wzorcem behawioralnym, który umożliwia przekazywanie zapytań między obiektami, aż do momentu, gdy któreś z nich zdecyduje, że jest w stanie obsłużyć dane zapytanie. Wzorzec ten pozwala na elastyczne i dynamiczne przypisanie obsługi żądania w trakcie działania programu.

Wzorzec łańcuch odpowiedzialności opiera się na tworzeniu łańcucha obiektów, z których każdy może obsłużyć daną prośbę lub przekazać ją dalej do następnego obiektu w łańcuchu.

Działa to na zasadzie hierarchii. W tym wzorcu klient przekazuje żądanie do pierwszego obiektu
w łańcuchu. Jeśli obiekt ten nie może obsłużyć żądania, przekazuje je do wyższego w hierarchii obiektu i tak dalej, aż żądanie zostanie obsłużone lub dojdziemy do końca łańcucha.

Podstawowe elementy wzorca Łańcuch odpowiedzialności

Handler – klasa bazowa, która określa podstawowe metody i właściwości, które muszą zostać zaimplementowane przez każdy kolejny element łańcucha

BaseHandler (opcjonalnie)- bazowy obiekt obsługujący, który zawiera kod przygotowawczy który będzie wspólny dla każdego elementu łańcucha odpowiedzialności.

ConcreteHandler – jest to konkretna implementacja klasy Handler, która ma za zadanie obsłużyć żądanie lub przekazać je dalej do następnego elementu w łańcuchu.

Client – jest to klasa lub obiekt, który przekazuje żądanie do pierwszego elementu w łańcuchu.

Przykład implementacji we wzorcu Łańcuch odpowiedzialności

public abstract class Handler {
   protected Handler nextHandler;
 
   public void setNextHandler(Handler handler) {
      nextHandler = handler;
   }
 
   public abstract void handleRequest(Request request);
}
public class Request {
   private String type;
   private String data;
 
   public Request(String type, String data) {
      this.type = type;
      this.data = data;
   }
 
   public String getType() {
      return type;
   }
 
   public String getData() {
      return data;
   }
}
Pierwszy Handler
public class ConcreteHandler1 extends Handler {
   @Override
   public void handleRequest(Request request) {
      if (request.getType().equals("type1")) {
         System.out.println("Request handled by ConcreteHandler1");
      } else {
         if (nextHandler != null) {
            nextHandler.handleRequest(request);
         }
      }
   }
}

Drugi Handler
public class ConcreteHandler2 extends Handler {
   @Override
   public void handleRequest(Request request) {
      if (request.getType().equals("type2")) {
         System.out.println("Request handled by ConcreteHandler2");
      } else {
         if (nextHandler != null) {
            nextHandler.handleRequest(request);
         }
      }
   }
}

Klasa Client
public class Client {
   public static void main(String[] args) {
      Handler handler1 = new ConcreteHandler1();
      Handler handler2 = new ConcreteHandler2();
 
      handler1.setNextHandler(handler2);
 
      Request request1 = new Request("type1", "data1");
      Request request2 = new Request("type2", "data2");
      Request request3 = new Request("type3", "data3");
 
      handler1.handleRequest(request1);
      handler1.handleRequest(request2);
      handler1.handleRequest(request3);
   }
}

W tym przykładzie mamy trzy klasy: Handler, ConcreteHandler1 i ConcreteHandler2. Klasa Handler jest klasą abstrakcyjną, która definiuje metodę handleRequest i pole nextHandler.
Klasy ConcreteHandler1 i ConcreteHandler2 dziedziczą po klasie Handler i implementują metodę handleRequest. Klasa Client tworzy instancje obu klas ConcreteHandler1 i ConcreteHandler2, łączy je ze sobą i przekazuje im trzy różne żądania.

Po uruchomieniu programu otrzymujemy następujące wyniki:

Request handled by ConcreteHandler1
Request handled by ConcreteHandler2

Wynika to z faktu, że pierwsze żądanie jest typu „type1„, który jest obsługiwany przez ConcreteHandler1, a drugie żądanie jest typu „type2„, który jest obsługiwany przez ConcreteHandler2. Trzecie żądanie zostało zignorowane, ponieważ żaden z handlerów nie obsługiwał tego typu.