Modèle de conception de chaîne de responsabilité en Java

Modèle de conception de chaîne de responsabilité en Java

1. introduction

Dans cet article, nous allons jeter un oeil à unbehavioral design pattern:Chain of Responsibility largement utilisé.

Nous pouvons trouver plus de modèles de conception dans nosprevious article.

2. Chaîne de responsabilité

Wikipedia définit la chaîne de responsabilité comme un modèle de conception consistant en «une source d'objets de commande et une série d'objets de traitement».

Chaque objet de traitement de la chaîne est responsable d'un certain type de commande et le traitement est terminé. Il transmet la commande au processeur suivant de la chaîne.

Le modèle de chaîne de responsabilité est pratique pour:

  • Découpler un émetteur et un destinataire d'une commande

  • Choisir une stratégie de traitement au moment du traitement

Voyons donc un exemple simple du motif.

3. Exemple

Nous allons utiliser Chain of Responsibility pour créer une chaîne de traitement des demandes d'authentification.

Ainsi, le fournisseur d'authentification d'entrée sera lecommand, et chaque processeur d'authentification sera un objetprocessor distinct.

Commençons par créer une classe de base abstraite pour nos processeurs:

public abstract class AuthenticationProcessor {

    public AuthenticationProcessor nextProcessor;

    // standard constructors

    public abstract boolean isAuthorized(AuthenticationProvider authProvider);
}

Ensuite, créons des processeurs concrets qui étendentAuthenticationProcessor:

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;
    }
}

Ici, nous avons créé deux processeurs concrets pour nos demandes d'autorisation entrantes:UsernamePasswordProcessor etOAuthProcessor.

Pour chacun d'eux, nous avons remplacé la méthodeisAuthorized.

Créons maintenant quelques tests:

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()));
    }
}

L'exemple ci-dessus crée une chaîne de processeurs d'authentification:UsernamePasswordProcessor → OAuthProcessor. Dans le premier test, l'autorisation réussit et dans l'autre, elle échoue.

Tout d'abord,UsernamePasswordProcessor vérifie si le fournisseur d'authentification est une instance deUsernamePasswordProvider.

N'étant pas l'entrée attendue,UsernamePasswordProcessor délègue àOAuthProcessor.

Enfin, leOAuthProcessor traite la commande. Dans le premier test, il y a correspondance et le test réussit. Dans le second cas, il n'y a plus de processeurs dans la chaîne et le test échoue.

4. Principes de mise en œuvre

Nous devons garder à l'esprit quelques principes importants lors de la mise en œuvre de la chaîne de responsabilité:

  • Chaque processeur de la chaîne aura sa mise en œuvre pour traiter une commande

    • Dans notre exemple ci-dessus, tous les processeurs ont leur implémentation deisAuthorized

  • Chaque processeur de la chaîne doit avoir une référence au processeur suivant

    • Au-dessus,UsernamePasswordProcessor délègue àOAuthProcessor

  • Chaque processeur est responsable de la délégation au processeur suivant, alors méfiez-vous des commandes abandonnées

    • Encore une fois dans notre exemple, si la commande est une instance deSamlProvider alors la demande peut ne pas être traitée et sera non autorisée

  • Les processeurs ne doivent pas former un cycle récursif

    • Dans notre exemple, nous n'avons pas de cycle dans notre chaîne:UsernamePasswordProcessor → OAuthProcessor. Mais, si nous définissons explicitementUsernamePasswordProcessor comme prochain processeur deOAuthProcessor,then we end up with a cycle in our chain:UsernamePasswordProcessor → OAuthProcessor → UsernamePasswordProcessor. Prendre le processeur suivant dans le constructeur peut vous aider

  • Un seul processeur de la chaîne gère une commande donnée

    • Dans notre exemple, si une commande entrante contient une instance deOAuthTokenProvider, alors seulementOAuthProcessor gérera la commande

5. Utilisation dans le monde réel

Dans le monde Java, nous bénéficions chaque jour de la chaîne de responsabilité. One such classic example is Servlet Filters inJava qui permettent à plusieurs filtres de traiter une requête HTTP. Bien que dans ce cas,each filter invokes the chain instead of the next filter.

Jetons un coup d'œil à l'extrait de code ci-dessous pour mieux comprendre ce modèle dans les filtres de servlet:

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);
    }
}

Comme indiqué dans l'extrait de code ci-dessus, nous devons appeler la méthodedoFilter deFilterChain afin de transmettre la requête au processeur suivant de la chaîne.

6. Désavantages

Et maintenant que nous avons vu à quel point la chaîne de responsabilité est intéressante, gardons à l'esprit certains inconvénients:

  • Surtout, il peut se briser facilement:

    • si un processeur ne parvient pas à appeler le processeur suivant, la commande est abandonnée

    • si un processeur appelle un mauvais processeur, cela peut entraîner un cycle

  • Il peut créer des traces de pile profondes pouvant affecter les performances.

  • Cela peut entraîner une duplication du code sur tous les processeurs, ce qui augmente la maintenance

7. Conclusion

Dans cet article, nous avons parlé de la chaîne de responsabilité, de ses forces et de ses faiblesses, à l'aide d'une chaîne autorisant les demandes d'authentification entrantes.

Et, comme toujours, le code source peut être trouvéover on GitHub.