1. 序章

この記事では、広く使用されている動作設計パターン Chain ofResponsibilityを見ていきます。

前の記事でより多くのデザインパターンを見つけることができます。

2. 責任の連鎖

Wikipedia は、Chain of Responsibilityを、「コマンドオブジェクトのソースと一連の処理オブジェクト」で構成されるデザインパターンとして定義しています。

チェーン内の各処理オブジェクトは、特定のタイプのコマンドを担当し、処理が実行されると、コマンドがチェーン内の次のプロセッサに転送されます。

Chain of Responsibilityパターンは、次の場合に便利です。

  • コマンドの送信者と受信者を分離する
  • 処理時に処理戦略を選択する

それでは、パターンの簡単な例を見てみましょう。

3. 例

Chain of Responsibilityを使用して、認証要求を処理するためのチェーンを作成します。

したがって、入力認証プロバイダーはコマンドになり、各認証プロセッサーは個別のプロセッサーオブジェクトになります。

まず、プロセッサの抽象基本クラスを作成しましょう。

public abstract class AuthenticationProcessor {

    public AuthenticationProcessor nextProcessor;
    
    // standard constructors

    public abstract boolean isAuthorized(AuthenticationProvider authProvider);
}

次に、AuthenticationProcessorを拡張する具象プロセッサを作成しましょう。

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

ここでは、着信承認リクエスト用に2つの具体的なプロセッサを作成しました。 UsernamePasswordProcessor OAuthProcessor

それぞれについて、isAuthorizedメソッドをオーバーライドします。

次に、いくつかのテストを作成しましょう。

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

上記の例では、認証プロセッサのチェーンを作成します: UsersnamePasswordProcessor->OAuthProcessor。 最初のテストでは承認は成功し、他のテストでは失敗します。

まず、 UsersnamePasswordProcessor は、認証プロバイダーがUsersnamePasswordProviderのインスタンスであるかどうかを確認します。

予期された入力ではないため、UsersnamePasswordProcessorOAuthProcessorに委任します。

最後に、OAuthProcessorがコマンドを処理します。 最初のテストでは、一致があり、テストに合格します。 2番目の例では、チェーン内にこれ以上プロセッサがないため、テストは失敗します。

4. 実装原則

Chain of Responsibilityを実装する際には、いくつかの重要な原則を念頭に置く必要があります。

  • チェーン内の各プロセッサには、コマンドを処理するための実装があります
    • 上記の例では、すべてのプロセッサにisAuthorizedの実装があります。
  • チェーン内のすべてのプロセッサは、次のプロセッサを参照する必要があります
    • 上記では、UsersnamePasswordProcessorOAuthProcessorに委任します
  • 各プロセッサは次のプロセッサに委任する責任があるため、ドロップされたコマンドに注意してください
    • この例でも、コマンドが SamlProvider のインスタンスである場合、リクエストは処理されず、許可されない可能性があります
  • プロセッサは再帰的なサイクルを形成するべきではありません
    • この例では、チェーンにサイクルがありません。 UsernamePasswordProcessor-> OAuthProcessor ただし、明示的に設定すると UsernamePasswordProcessor 次のプロセッサとして OAuthProcessor、 それから私達は私達のチェーンのサイクルで終わります UsernamePasswordProcessor->OAuthProcessor->UsernamePasswordProcessor。 コンストラクターで次のプロセッサーを使用すると、これに役立ちます
  • チェーン内の1つのプロセッサのみが特定のコマンドを処理します
    • この例では、着信コマンドに OAuthTokenProvider のインスタンスが含まれている場合、OAuthProcessorのみがコマンドを処理します

5. 実世界での使用法

Javaの世界では、ChainofResponsibilityの恩恵を毎日受けています。 そのような典型的な例の1つは、 Java のサーブレットフィルターで、複数のフィルターがHTTPリクエストを処理できるようにします。 その場合でも、各フィルターは次のフィルターの代わりにチェーンを呼び出します。

サーブレットフィルタでこのパターンをよりよく理解するために、以下のコードスニペットを見てみましょう。

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

上記のコードスニペットに示されているように、チェーン内の次のプロセッサにリクエストを渡すには、FilterChaindoFilterメソッドを呼び出す必要があります。

6. 短所

そして、Chain of Responsibilityがいかに興味深いかを見てきましたが、いくつかの欠点を覚えておきましょう。

  • ほとんどの場合、簡単に壊れることがあります。
    • プロセッサが次のプロセッサの呼び出しに失敗した場合、コマンドはドロップされます
    • プロセッサが間違ったプロセッサを呼び出すと、サイクルが発生する可能性があります
  • 深いスタックトレースを作成する可能性があり、パフォーマンスに影響を与える可能性があります
  • プロセッサ間でコードが重複し、メンテナンスが増える可能性があります

7. 結論

この記事では、Chain of Responsibilityとその長所と短所について、チェーンを使用して着信認証要求を承認する方法について説明しました。

そして、いつものように、ソースコードはGitHubにあります。