Das Chain of Responsibility Pattern ist ein Verhaltensmuster, das es ermöglicht, Anfragen durch eine Kette von Handlers zu leiten. Jeder Handler in der Kette hat die Möglichkeit, die Anfrage zu verarbeiten oder sie an den nächsten Handler weiterzugeben. Dieses Muster reduziert die Kopplung zwischen Sender und Empfänger, indem es die Zuständigkeit für die Bearbeitung einer Anfrage verteilt. Es fördert die Flexibilität, indem neue Handler leicht hinzugefügt oder bestehende entfernt werden können.
Was ist das Chain of Responsibility Pattern?
Das Chain of Responsibility Pattern ermöglicht es, eine Anfrage entlang einer Kette von Objekten weiterzuleiten. Jeder Handler in der Kette kann die Anfrage entweder bearbeiten oder sie an den nächsten Handler weitergeben. Dieses Designmuster ermöglicht eine flexible Handhabung von Anfragen, ohne dass der Sender direkt mit einem konkreten Empfänger gekoppelt ist.
Das Muster besteht aus vier Hauptkomponenten:
- Handler: Eine abstrakte Klasse oder Schnittstelle, die eine Methode zum Bearbeiten oder Weitergeben der Anfrage definiert.
- ConcreteHandler: Eine konkrete Implementierung des Handlers, die die Anfrage entweder bearbeitet oder sie an den nächsten Handler weitergibt.
- Client: Der Client ist der Sender der Anfrage. Er erstellt die Anfrage und gibt sie an den ersten Handler in der Kette weiter.
- Chain of Handlers: Eine Reihe von Handlers, die in einer Kette angeordnet sind. Jeder Handler überprüft die Anfrage und entscheidet, ob er sie bearbeitet oder weitergibt.
Beispiel des Chain of Responsibility Patterns in C++
Angenommen, wir möchten ein System entwickeln, das verschiedene Arten von Fehlern verarbeitet. Jeder Fehler könnte von einem anderen Handler bearbeitet werden. Einige Fehler erfordern einfache Benachrichtigungen, während andere detaillierte Protokolle oder Fehlerbehebungen erfordern.
Hier ist ein einfaches Beispiel in C++:
#include <iostream>
#include <memory>
// Handler-Schnittstelle: Definiert die Bearbeitung der Anfrage
class Handler {
public:
virtual void handleRequest(int level) = 0;
virtual ~Handler() = default;
};
// ConcreteHandler 1: Bearbeitet Fehler der Stufe 1
class ConcreteHandler1 : public Handler {
private:
std::shared_ptr<Handler> nextHandler;
public:
void setNext(std::shared_ptr<Handler> next) {
nextHandler = next;
}
void handleRequest(int level) override {
if (level == 1) {
std::cout << "ConcreteHandler1 bearbeitet Fehler der Stufe 1\n";
} else if (nextHandler) {
nextHandler->handleRequest(level);
}
}
};
// ConcreteHandler 2: Bearbeitet Fehler der Stufe 2
class ConcreteHandler2 : public Handler {
private:
std::shared_ptr<Handler> nextHandler;
public:
void setNext(std::shared_ptr<Handler> next) {
nextHandler = next;
}
void handleRequest(int level) override {
if (level == 2) {
std::cout << "ConcreteHandler2 bearbeitet Fehler der Stufe 2\n";
} else if (nextHandler) {
nextHandler->handleRequest(level);
}
}
};
// ConcreteHandler 3: Bearbeitet Fehler der Stufe 3
class ConcreteHandler3 : public Handler {
private:
std::shared_ptr<Handler> nextHandler;
public:
void setNext(std::shared_ptr<Handler> next) {
nextHandler = next;
}
void handleRequest(int level) override {
if (level == 3) {
std::cout << "ConcreteHandler3 bearbeitet Fehler der Stufe 3\n";
} else if (nextHandler) {
nextHandler->handleRequest(level);
}
}
};
// Client-Code
int main() {
// Erstelle Handler
auto handler1 = std::make_shared<ConcreteHandler1>();
auto handler2 = std::make_shared<ConcreteHandler2>();
auto handler3 = std::make_shared<ConcreteHandler3>();
// Setze die Kette
handler1->setNext(handler2);
handler2->setNext(handler3);
// Fehler der Stufe 1
std::cout << "Fehler Stufe 1:\n";
handler1->handleRequest(1);
// Fehler der Stufe 2
std::cout << "\nFehler Stufe 2:\n";
handler1->handleRequest(2);
// Fehler der Stufe 3
std::cout << "\nFehler Stufe 3:\n";
handler1->handleRequest(3);
// Fehler der Stufe 4 (Keiner kann ihn bearbeiten)
std::cout << "\nFehler Stufe 4:\n";
handler1->handleRequest(4);
return 0;
}
Erklärung des C++-Beispiels
- Handler: Die abstrakte Klasse
Handler
stellt die MethodehandleRequest()
bereit, die von den konkreten Handlers überschrieben wird. Sie definiert auch eine Methode zum Setzen des nächsten Handlers in der Kette. - ConcreteHandler1, ConcreteHandler2, ConcreteHandler3: Diese konkreten Handler implementieren die
handleRequest()
-Methode und bearbeiten die Anfragen je nach Fehlerstufe. Wenn ein Handler eine Anfrage nicht bearbeiten kann, gibt er sie an den nächsten Handler weiter. - Client: Der Client erstellt die Handler und setzt sie in eine Kette. Er übergibt dann eine Anfrage an den ersten Handler. Jeder Handler entscheidet, ob er die Anfrage verarbeitet oder sie weiterleitet.
Vorteile des Chain of Responsibility Patterns
- Lose Kopplung: Der Sender ist nicht direkt an einen konkreten Handler gebunden. Die Kette der Handler übernimmt die Verantwortung, die Anfrage zu bearbeiten.
- Flexibilität: Es ist einfach, neue Handler hinzuzufügen oder bestehende zu entfernen. Der Client muss diese Änderungen nicht kennen.
- Verteilung der Verantwortung: Die Verantwortung für die Bearbeitung von Anfragen wird auf mehrere Handler verteilt. Das erleichtert die Wartung und Erweiterung des Systems.
- Verarbeitung durch mehrere Handler: Eine Anfrage kann von mehreren Handlers verarbeitet werden, abhängig von ihrer Art oder ihrem Kontext.
Nachteile des Chain of Responsibility Patterns
- Unklare Verantwortlichkeiten: Wenn die Kette von Handlers zu lang oder schlecht organisiert ist, kann es unklar werden, welcher Handler für die Anfrage verantwortlich ist.
- Leistungseinbußen: Wenn die Kette sehr lang ist, kann die Anfrage unnötig viele Handlers durchlaufen, bevor sie bearbeitet wird.
- Schwierigkeit bei Fehlerbehandlung: Wenn keiner der Handler die Anfrage bearbeiten kann, muss eine geeignete Fehlerbehandlung implementiert werden.
Fazit
Das Chain of Responsibility Pattern ist besonders nützlich, wenn Anfragen von verschiedenen Objekten verarbeitet werden müssen und jedes Objekt entscheiden kann, ob es die Anfrage bearbeiten möchte. Es ermöglicht eine flexible und erweiterbare Architektur, in der neue Handlers problemlos hinzugefügt werden können.
Das Beispiel in C++ zeigt, wie einfach es ist, das Muster zu implementieren, und verdeutlicht die Vorteile einer flexiblen Kettenstruktur. Dieses Muster eignet sich hervorragend für Szenarien wie Fehlerbehandlung, Event-Handling und Anfragenbearbeitung, bei denen verschiedene Verarbeitungseinheiten beteiligt sind.
Zusammenfassend lässt sich sagen, dass das Chain of Responsibility Pattern eine ausgezeichnete Möglichkeit bietet, Anfragen effizient und modular zu verarbeiten, während gleichzeitig die Kopplung zwischen den Komponenten minimiert wird.
Zurück zur Liste der Pattern: Liste der Design-Pattern