Kette der Verantwortung Design Pattern in Java

Entwurfsmuster für die Verantwortungskette in Java

1. Einführung

In diesem Artikel werfen wir einen Blick auf ein weit verbreitetesbehavioral design pattern:Chain of Responsibility.

Weitere Designmuster finden Sie in unserenprevious article.

2. Verantwortungskette

Wikipedia definiert die Verantwortungskette 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, leitet es den Befehl an den nächsten Prozessor in der Kette weiter.

Das Muster der Verantwortungskette ist nützlich für:

  • Entkopplung von Sender und Empfänger eines Befehls

  • Auswahl einer Verarbeitungsstrategie zur Verarbeitungszeit

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

3. Beispiel

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

Der Eingabeauthentifizierungsanbieter ist alsocommand, und jeder Authentifizierungsprozessor ist ein separatesprocessor-Objekt.

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, dieAuthenticationProcessor 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 undOAuthProcessor.

Für jeden haben wir dieisAuthorized-Methode überschrieben.

Lassen Sie uns nun einige 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, im anderen schlägt sie fehl.

Zunächst prüftUsernamePasswordProcessor, ob der Authentifizierungsanbieter eine Instanz vonUsernamePasswordProvider ist.

DaUsernamePasswordProcessornicht die erwartete Eingabe ist, delegiert es anOAuthProcessor.

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

4. Implementierungsprinzipien

Bei der Umsetzung der Verantwortungskette müssen wir 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 vonisAuthorized

  • Jeder Prozessor in der Kette sollte auf den nächsten Prozessor verweisen

    • Oben delegiertUsernamePasswordProcessor anOAuthProcessor

  • 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 vonSamlProvider 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 jedochUsernamePasswordProcessor explizit als nächsten Prozessor vonOAuthProcessor,then we end up with a cycle in our chain festlegen: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 vonOAuthTokenProvider enthält, wird der Befehl nur vonOAuthProcessor verarbeitet

5. Verwendung in der realen Welt

In der Java-Welt profitieren wir jeden Tag von Chain of Responsibility. One such classic example is Servlet Filters inJava, mit denen mehrere Filter eine HTTP-Anforderung verarbeiten können. In diesem Fall isteach filter invokes the chain instead of the next filter.

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 Codefragment zu sehen ist, müssen wir diedoFilter-Methode vonFilterChainaufrufen, um die Anforderung an den nächsten Prozessor in der Kette weiterzuleiten.

6. Nachteile

Und jetzt, da wir gesehen haben, wie interessant die Verantwortungskette 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 verworfen

    • 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 der Code prozessorübergreifend dupliziert wird, was die Wartung erhöht

7. Fazit

In diesem Artikel haben wir über die Verantwortungskette und ihre Stärken und Schwächen mit Hilfe einer Kette zur Autorisierung eingehender Authentifizierungsanforderungen gesprochen.

Und wie immer kann der Quellcodeover on GitHub gefunden werden.