Entwurfsmuster für die Verantwortungskette in Java

1. Einleitung

In diesem Artikel werfen wir einen Blick auf ein weit verbreitetes Verhaltensmuster : Chain of Responsibility .

Weitere Designmuster finden Sie in unserem vorherigen Artikel.

2. Kette der Verantwortung

Wikipedia definiert Chain of Responsibility als ein Entwurfsmuster, das aus „einer Quelle von Befehlsobjekten und einer Reihe von Verarbeitungsobjekten“ besteht.

Jedes Verarbeitungsobjekt in der Kette ist für einen bestimmten Befehlstyp verantwortlich. Wenn die Verarbeitung abgeschlossen ist, wird der Befehl an den nächsten Prozessor in der Kette weitergeleitet.

Das Muster der Verantwortungskette ist praktisch für:

  • Entkopplung von Sender und Empfänger eines Befehls
  • Auswahl einer Verarbeitungsstrategie zur Verarbeitungszeit

Schauen wir uns also ein einfaches Beispiel für das Muster an.

3. Beispiel

Wir werden Chain of Responsibility verwenden, um eine Kette für die Bearbeitung von Authentifizierungsanforderungen zu erstellen.

Der Eingabeauthentifizierungsanbieter ist also der Befehl , und jeder Authentifizierungsprozessor ist ein separates Prozessorobjekt .

Erstellen wir zunächst eine abstrakte Basisklasse für unsere Prozessoren:

public abstract class AuthenticationProcessor { public AuthenticationProcessor nextProcessor; // standard constructors public abstract boolean isAuthorized(AuthenticationProvider authProvider); }

Als Nächstes erstellen wir konkrete Prozessoren, die AuthenticationProcessor erweitern :

public class OAuthProcessor extends AuthenticationProcessor { public OAuthProcessor(AuthenticationProcessor nextProcessor) { super(nextProcessor); } @Override public boolean isAuthorized(AuthenticationProvider authProvider) { if (authProvider instanceof OAuthTokenProvider) { return true; } else if (nextProcessor != null) { return nextProcessor.isAuthorized(authProvider); } return false; } }
public class UsernamePasswordProcessor extends AuthenticationProcessor { public UsernamePasswordProcessor(AuthenticationProcessor nextProcessor) { super(nextProcessor); } @Override public boolean isAuthorized(AuthenticationProvider authProvider) { if (authProvider instanceof UsernamePasswordProvider) { return true; } else if (nextProcessor != null) { return nextProcessor.isAuthorized(authProvider); } return false; } }

Hier haben wir zwei konkrete Prozessoren für unsere eingehenden Autorisierungsanforderungen erstellt: UsernamePasswordProcessor und OAuthProcessor .

Für jeden haben wir die isAuthorized- Methode überschrieben .

Lassen Sie uns nun ein paar Tests erstellen:

public class ChainOfResponsibilityTest { private static AuthenticationProcessor getChainOfAuthProcessor() { AuthenticationProcessor oAuthProcessor = new OAuthProcessor(null); return new UsernamePasswordProcessor(oAuthProcessor); } @Test public void givenOAuthProvider_whenCheckingAuthorized_thenSuccess() { AuthenticationProcessor authProcessorChain = getChainOfAuthProcessor(); assertTrue(authProcessorChain.isAuthorized(new OAuthTokenProvider())); } @Test public void givenSamlProvider_whenCheckingAuthorized_thenSuccess() { AuthenticationProcessor authProcessorChain = getChainOfAuthProcessor(); assertFalse(authProcessorChain.isAuthorized(new SamlTokenProvider())); } }

Im obigen Beispiel wird eine Kette von Authentifizierungsprozessoren erstellt: UsernamePasswordProcessor -> OAuthProcessor . Im ersten Test ist die Autorisierung erfolgreich und im anderen fehlgeschlagen.

Zunächst überprüft UsernamePasswordProcessor , ob der Authentifizierungsanbieter eine Instanz von UsernamePasswordProvider ist .

Da UsernamePasswordProcessor nicht die erwartete Eingabe ist, wird er an OAuthProcessor delegiert .

Zuletzt verarbeitet der OAuthProcessor den Befehl. Im ersten Test gibt es eine Übereinstimmung und der Test besteht. Im zweiten Fall befinden sich keine Prozessoren mehr in der Kette, und der Test schlägt fehl.

4. Implementierungsprinzipien

Wir müssen bei der Implementierung der Verantwortungskette einige wichtige Grundsätze berücksichtigen:

  • Jeder Prozessor in der Kette hat seine Implementierung zur Verarbeitung eines Befehls
    • In unserem obigen Beispiel haben alle Prozessoren die Implementierung von isAuthorized
  • Jeder Prozessor in der Kette sollte auf den nächsten Prozessor verweisen
    • Oben delegiert UsernamePasswordProcessor an OAuthProcessor
  • Jeder Prozessor ist für die Delegierung an den nächsten Prozessor verantwortlich. Achten Sie daher auf abgelegte Befehle
    • Wenn der Befehl in unserem Beispiel eine Instanz von SamlProvider ist, wird die Anforderung möglicherweise nicht verarbeitet und ist nicht autorisiert
  • Prozessoren sollten keinen rekursiven Zyklus bilden
    • In unserem Beispiel haben wir keinen Zyklus in unserer Kette: UsernamePasswordProcessor -> OAuthProcessor . Wenn wir jedoch UsernamePasswordProcessor explizit als nächsten Prozessor von OAuthProcessor festlegen, erhalten wir einen Zyklus in unserer Kette : UsernamePasswordProcessor -> OAuthProcessor -> UsernamePasswordProcessor. Die Verwendung des nächsten Prozessors im Konstruktor kann dabei helfen
  • Nur ein Prozessor in der Kette verarbeitet einen bestimmten Befehl
    • Wenn in unserem Beispiel ein eingehender Befehl eine Instanz von OAuthTokenProvider enthält , wird der Befehl nur von OAuthProcessor verarbeitet

5. Verwendung in der realen Welt

In der Java-Welt profitieren wir jeden Tag von der Verantwortungskette. Ein solches klassisches Beispiel sind Servlet-Filter in Java , mit denen mehrere Filter eine HTTP-Anforderung verarbeiten können. In diesem Fall ruft jeder Filter die Kette anstelle des nächsten Filters auf.

Schauen wir uns das folgende Codefragment an, um dieses Muster in Servlet-Filtern besser zu verstehen :

public class CustomFilter implements Filter { public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // process the request // pass the request (i.e. the command) along the filter chain chain.doFilter(request, response); } }

Wie im obigen Code-Snippet zu sehen ist, müssen wir die doFilter - Methode von FilterChain aufrufen , um die Anforderung an den nächsten Prozessor in der Kette weiterzuleiten.

6. Nachteile

Und jetzt, da wir gesehen haben, wie interessant Chain of Responsibility ist, sollten wir einige Nachteile berücksichtigen:

  • Meistens kann es leicht kaputt gehen:
    • Wenn ein Prozessor den nächsten Prozessor nicht aufruft, wird der Befehl gelöscht
    • Wenn ein Prozessor den falschen Prozessor aufruft, kann dies zu einem Zyklus führen
  • Es können tiefe Stapelspuren erstellt werden, die die Leistung beeinträchtigen können
  • Dies kann dazu führen, dass Code prozessorübergreifend dupliziert wird, was die Wartung erhöht

7. Fazit

In diesem Artikel haben wir mithilfe einer Kette über die Verantwortungskette und ihre Stärken und Schwächen gesprochen, um eingehende Authentifizierungsanforderungen zu autorisieren.

Und wie immer ist der Quellcode auf GitHub zu finden.