(Credits: Blog von Vishal Chovatiya)
„Das Chain-of-Responsibility-Pattern leitet eine Anfrage entlang einer Kette von Objekten weiter, bis eines dieser Objekte sie bearbeiten kann.”
Beim Chain-of-Responsibility-Pattern wird eine Anfrage nicht direkt an ein bestimmtes Objekt zur Bearbeitung übergeben, sondern an das erste Glied einer Verarbeitungskette. Jedes Glied entscheidet selbst, ob es für die Bearbeitung der Anfrage zuständig ist. Ist dies der Fall, verarbeitet es die Anfrage und beendet damit die Weiterleitung. Andernfalls gibt es die Anfrage an das nächste Glied der Kette weiter. Dadurch muss der Sender einer Anfrage nicht wissen, welches konkrete Objekt für ihre Bearbeitung zuständig ist.
Die einzelnen Glieder der Kette sind über eine gemeinsame Schnittstelle miteinander verbunden und können in der Regel flexibel kombiniert oder ausgetauscht werden. So lassen sich beispielsweise unterschiedliche Verarbeitungsstufen oder Zuständigkeiten hintereinander anordnen.
Ein wesentlicher Vorteil des Patterns ist die geringe Kopplung zwischen dem Absender einer Anfrage und ihrem konkreten Bearbeiter.
Typische Einsatzgebiete sind zum Beispiel die Verarbeitung von Ereignissen in GUI-Frameworks, mehrstufige Validierungs- oder Genehmigungsprozesse
sowie Middleware-Ketten in Webanwendungen. In C++ wird die Kette meist über eine gemeinsame Basisklasse mit einem Zeiger (ggf. std::shared_ptr)
auf den nächsten Handler sowie einer virtuellen handle()-Methode realisiert.
Das folgende UML-Diagramm beschreibt eine Implementierung des Chain of Responsibility Patterns. Es besteht im Wesentlichen aus drei Teilen:
- Client: Diese Klasse übergibt das Ereignis (die Anforderung) an das erste Objekt in der Kette der Verarbeitungsobjekte.
- HandlerBase: Repräsentiert eine Schnittstelle oder Basisklasse für die konkreten Handler einer Verarbeitungskette. Typischerweise enthält es eine Instanzvariable, die auf das nächste Handlerobjekt in der Verarbeitungskette verweist.
- ConcreteHandler: Konkrete Implementierung der
HandlerBase-Klasse.
Abbildung 1: Schematische Darstellung des Chain of Responsibility Patterns.
Wir betrachten als reale Anwendung dieses Entwurfsmusters die (triviale) Realisierung eines Anmeldeprozesses (Login). Dieser erfordert eine bestimmte Anzahl von Schritten, um erfolgreich abgeschlossen werden zu können, wie z.B. die Eingabe von Benutzername, Passwort, den Abgleich mit einem Captcha usw.
Ein erster Aufruf von
login->authenticate();löst eine „Chain of Responsibility” Kette aus, um jeden für die Anmeldung erforderlichen Schritt einzeln in die Wege zu leiten.
Man kann den Anmeldeprozess auch auf einfachste Weise um weitere Schritte ergänzen, z.B. um einen Captcha-Abgleich hinzuzufügen, so wie dies für Benutzername und Passwort im Quellcode demonstriert wird.
- Klasse
Authentication– KlasseHandlerBase - Methode
authenticate– MethodehandleRequest - Methode
nextAuthentication– MethodesetSuccessor - Klasse
AuthenticateUserName– KlasseConcreteHandler - Klasse
AuthenticatePassword– KlasseConcreteHandler
Quellcode zum 'Authentication' Beispiel – Anwendungsfall des Chain of Responsibility Patterns.
Hinweis: In der Realisierung des Beispiels sind zwei Implementierungsdetails zu beachten:
- Einsatz von Klasse
std::unique_ptr<>. std::move:std::unique_ptr<>-Objekte unterstützen nur die Verschiebe-Semantik.
Ausgabe:
Authentication of User Name succeeded!
Authentication of Password succeeded!
Authentication succeeded!
Die Anregungen zum konzeptionellen Beispiel finden Sie unter
https://refactoring.guru/design-patterns
und
vor.
Das Real-World-Beispiel kann hier im Original nachgelesen werden.
