Chain of Responsibility (Felelősséglánc)
Kategória: Viselkedési minta (Behavioral Pattern)
Lényege és célja
A Chain of Responsibility minta egy kérést egy láncba kötött feldolgozók (handler) sorozatán enged végighaladni mindaddig, amíg valamelyikük fel nem dolgozza. A kérés feladójának nem kell tudnia, hogy a láncban ki fogja végül lekezelni az ügyet – csak az első elemnek adja át.
A kulcs ötlet: minden handler saját maga dönti el, hogy meg tudja-e oldani a problémát. Ha igen, lezárja a folyamatot; ha nem, továbbpasszolja a kérést a következőnek.
Miért jobb ez, mint egy nagy if-else?
Egy ügyfélszolgálati példán: súlyosság alapján Bot → Operátor → Menedzser sorrendben próbáljuk eszkalálni. Ha ezt egy nagy if-else-ben írnánk meg, a feladó össze lenne csatolva az összes szinttel. A láncban:
- Laza csatolás: a feladó csak a lánc első elemét ismeri, a többiről nem tud semmit.
- Bővíthetőség (Open/Closed): új szintet (pl.
VipManagerHandler) úgy is be lehet szúrni, hogy a meglévő handler-ek kódjához nem kell hozzányúlni. - Konfigurálhatóság: a lánc sorrendje és összetétele futásidőben szabadon átalakítható.
Mikor használjuk?
- Amikor egy kérést több, eltérő szabályokkal rendelkező objektum közül egynek (vagy akár többnek) kell feldolgoznia, és előre nem tudjuk, melyiknek.
- Amikor el akarunk kerülni egy hatalmas, központi
if-elseblokkot, ami a feldolgozó kiválasztásáról dönt (pl. eszkalációs szintek, validáló pipeline, middleware, log-szűrők). - Amikor a feldolgozási sorrend vagy a résztvevők listája dinamikusan változhat (pl. konfigurációból építjük össze).
Mermaid Diagram
A diagramon látható az absztrakt SupportHandler és a konkrét handler-ek kapcsolata. A nextHandler mező az önreferenciás kompozíció, ami a láncot összefűzi.
classDiagram
class SupportHandler {
<<abstract>>
#nextHandler: SupportHandler
+setNext(h: SupportHandler) SupportHandler
#handleRequest(issue: String, severity: int) String
}
class BotHandler {
#handleRequest(issue, severity) String
}
class OperatorHandler {
#handleRequest(issue, severity) String
}
class ManagerHandler {
#handleRequest(issue, severity) String
}
class Client
BotHandler --|> SupportHandler : "örökli"
OperatorHandler --|> SupportHandler : "örökli"
ManagerHandler --|> SupportHandler : "örökli"
%% Önmagára mutató kompozíció: a lánc következő eleme
SupportHandler o--> SupportHandler : "next"
Client --> SupportHandler : "csak az első elemet ismeri" Forráskód
package behavioral.chain_of_responsibility;
import java.util.Objects;
abstract class SupportHandler {
protected SupportHandler nextHandler;
public final SupportHandler setNext(SupportHandler supportHandler) {
if (nextHandler != null) {
var lastInChain = nextHandler;
while (lastInChain.nextHandler != null) {
lastInChain = lastInChain.nextHandler;
}
lastInChain.nextHandler = supportHandler;
} else {
nextHandler = supportHandler;
}
return this;
}
protected String handleRequest(String issue, int severity) {
if (Objects.nonNull(nextHandler)) {
return nextHandler.handleRequest(issue, severity);
} else {
return "Sajnos a problémát nem tudtuk megoldani: [" + issue + "]";
}
}
}
class BotHandler extends SupportHandler {
@Override
protected String handleRequest(String issue, int severity) {
if (severity <= 1) {
return "A Bot automatikusan megoldotta a problémát: [" + issue + "]";
} else {
return super.handleRequest(issue, severity);
}
}
}
class OperatorHandler extends SupportHandler {
@Override
protected String handleRequest(String issue, int severity) {
if (severity <= 2) {
return "Az Operátor lekezelte az ügyet: [" + issue + "]";
} else {
return super.handleRequest(issue, severity);
}
}
}
class ManagerHandler extends SupportHandler {
@Override
protected String handleRequest(String issue, int severity) {
if (severity <= 3) {
return "A Menedzser egyedi elbírálással megoldotta: [" + issue + "]";
} else {
return super.handleRequest(issue, severity);
}
}
}
class Main {
static void main() {
/*
Gof szerint mindig azzal kéne viszatérni amit kaptunk paraméterbe és kliensen kéne az elsőt példányosítani
külön, majd ahhoz setnextelni új sorban a többiket, viszont ha így írom meg a setnextet így az elspőhöz
lehet láncolni az összeset 1 kifejezésben, nekem így jobban tetszik.
*/
var result = new BotHandler()
.setNext(new OperatorHandler())
.setNext(new ManagerHandler());
System.out.println(result.handleRequest("Jelszó visszaállítás", 0));
System.out.println(result.handleRequest("Jelszó visszaállítás", 2));
System.out.println(result.handleRequest("Jelszó visszaállítás", 4));
}
}