Zuständigkeitskette
Die Zuständigkeitskette (englisch chain of responsibility) ist ein in der Softwareentwicklung eingesetztes Entwurfsmuster. Es gehört zur Kategorie der Verhaltensmuster (englisch behavioral design patterns) und wird für Algorithmen verwendet. Dabei dient es der Entkopplung des Auslösers einer Anfrage mit seinem Empfänger.[1] Das Muster ist eines der sogenannten GoF-Muster (siehe Viererbande, Gang of Four).
Verwendung
BearbeitenMehrere Objekte werden hintereinander geschaltet (miteinander verkettet), um gemeinsam eine eingehende Anfrage bearbeiten zu können. Diese Anfrage wird an der Kette entlang geleitet, bis eines der Objekte die Anfrage beantworten kann. Der Klient, von dem die Anfrage ausgeht, hat dabei keine Kenntnis darüber, von welchem Objekt die Anfrage beantwortet werden wird.
UML-Diagramm
BearbeitenAkteure
BearbeitenBei einer Zuständigkeitskette spielen drei Akteure eine Rolle:
- Bearbeiter, der ein Interface für die Anfragen definiert
- konkreter Bearbeiter, der alle Anfragen bearbeitet, für die er selbst zuständig ist und alle anderen Anfragen an das nächste Kettenglied (den nächsten Bearbeiter) weiterleitet
- Klient, der die Anfrage an irgendeinem konkreten Bearbeiter initiiert.
Vor- und Nachteile
BearbeitenEin Vorteil ist, dass der Klient den tatsächlich zuständigen Bearbeiter nicht kennen muss. Selbst die Kettenglieder müssen nur ihren direkten Nachfolger und nicht den Gesamt-Aufbau der Kette kennen. Dies führt zu einer geringeren Kopplung. Außerdem kann die Zuständigkeit von Objekten für bestimmte Anfragen verändert werden, ohne dass potenzielle Klienten davon in Kenntnis gesetzt werden müssen.
Es gibt auf der anderen Seite keine Garantie, dass die Anfrage tatsächlich bearbeitet wird. Wenn das letzte Glied der Kette eine Anfrage erhält, für die es ebenfalls nicht zuständig ist, wird die Anfrage nach obigem Muster verworfen. Dies muss durch eine entsprechende Fallbehandlung abgefangen werden.
Es muss sichergestellt werden, dass jeder Bearbeiter in der Kette nur einmal vorkommt, sonst entstehen Kreise und das Programm bleibt in einer Endlosschleife hängen.
Beispiel
BearbeitenDiese C++11 Implementierung basiert auf der vor C++98 Implementierung im Buch Entwurfsmuster.
#include <iostream>
#include <memory>
typedef int Thema;
constexpr Thema KEIN_HILFE_THEMA = -1;
// definiert eine Schnittstelle zur Bearbeitung von Anfragen.
class HilfeBearbeiter { // Bearbeiter
public:
HilfeBearbeiter(HilfeBearbeiter* h = nullptr, Thema t = KEIN_HILFE_THEMA)
: nachfolger(h), thema(t) {}
virtual bool hatHilfe() {
return thema != KEIN_HILFE_THEMA;
}
virtual void setBearbeiter(HilfeBearbeiter*, Thema) {}
virtual void bearbeiteHilfe() {
std::cout << "HilfeBearbeiter::bearbeiteHilfe\n";
// (optional) implementiert eine Verbindung zum Nachfolgeobjekt.
if (nachfolger != nullptr) {
nachfolger->bearbeiteHilfe();
}
}
virtual ~HilfeBearbeiter() = default;
HilfeBearbeiter(const HilfeBearbeiter&) = delete; // Dreierregel
HilfeBearbeiter& operator=(const HilfeBearbeiter&) = delete;
private:
HilfeBearbeiter* nachfolger;
Thema thema;
};
class Widget : public HilfeBearbeiter {
public:
Widget(const Widget&) = delete; // Dreierregel
Widget& operator=(const Widget&) = delete;
protected:
Widget(Widget* w, Thema t = KEIN_HILFE_THEMA)
: HilfeBearbeiter(w, t), elternObjekt(nullptr) {
elternObjekt = w;
}
private:
Widget* elternObjekt;
};
class Button : public Widget { // KonkreterBearbeiter
public:
Button(std::shared_ptr<Widget> h, Thema t = KEIN_HILFE_THEMA) : Widget(h.get(), t) {}
virtual void bearbeiteHilfe() {
// wenn der KonkreteBearbeiter die Anfrage bearbeiten kann, tut er es auch; andernfalls leitet er die Anfrage an das Nachfolgeobjekt weiter.
std::cout << "Button::bearbeiteHilfe\n";
if (hatHilfe()) {
// arbeitet genau die Anfrage ab, für die er zuständig ist.
} else {
// kann auf seinen Nachfolger zugreifen.
HilfeBearbeiter::bearbeiteHilfe();
}
}
};
class Dialog : public Widget { // KonkreterBearbeiter
public:
Dialog(std::shared_ptr<HilfeBearbeiter> h, Thema t = KEIN_HILFE_THEMA) : Widget(nullptr) {
setBearbeiter(h.get(), t);
}
virtual void bearbeiteHilfe() {
std::cout << "Dialog::bearbeiteHilfe\n";
if(hatHilfe()) {
// biete Hilfsinformationen für den Dialog an
} else {
HilfeBearbeiter::bearbeiteHilfe();
}
}
};
class Anwendung : public HilfeBearbeiter {
public:
Anwendung(Thema t) : HilfeBearbeiter(nullptr, t) {}
virtual void bearbeiteHilfe() {
std::cout << "Anwendung::bearbeiteHilfe\n";
// zeige eine Liste von Hilfsthemen an
}
};
int main() {
constexpr Thema DRUCKE_THEMA = 1;
constexpr Thema PAPIER_ORIENTIERUNG_THEMA = 2;
constexpr Thema ANWENDUNG_THEMA = 3;
// Die Smart pointers verhindern Memory leaks.
std::shared_ptr<Anwendung> application = std::make_shared<Anwendung>(ANWENDUNG_THEMA);
std::shared_ptr<Dialog> dialog = std::make_shared<Dialog>(application, DRUCKE_THEMA);
std::shared_ptr<Button> button = std::make_shared<Button>(dialog, PAPIER_ORIENTIERUNG_THEMA);
button->bearbeiteHilfe();
}
Die Programmausgabe ist:
Button::bearbeiteHilfe
Verwandte Entwurfsmuster
BearbeitenEin verwandtes Entwurfsmuster ist der Decorator: Vor oder nachdem eine Anfrage weitergeleitet wird, können zusätzliche Operationen erfolgen, wie zum Beispiel Gültigkeitsprüfungen. Außerdem ähnlich ist das Kompositum. Dabei wird die Anfrage so lange vom Child zum Parent weitergereicht, bis sie beantwortet wird oder kein weiteres Objekt folgt. Eine weitere Möglichkeit ergibt sich durch einen Iterator über Objekte mit Schablonenmethoden.
Einzelnachweise
Bearbeiten- ↑ Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: Entwurfsmuster. 5. Auflage. Addison-Wesley, 1996, ISBN 3-8273-1862-9, S. 410.