責任チェーンパターンの実装と応用

責任チェーン(Chain of Responsibility)パターンは、複数のオブジェクトが処理を順次受け渡すことで、リクエストを処理するデザインパターンです。この記事では、承認フローを題材にしながら、責任チェーンパターンの仕組みと実装方法について詳しく見ていきます。

責任チェーンとは

責任チェーンパターンは、複数のオブジェクトがそれぞれ前後のオブジェクトへの参照を持ち、リクエストが適切なハンドラによって処理されるまでチェーン上で伝播していくというものです。クライアント側は、どのハンドラが最終的に処理を行うかを知る必要はありません。

たとえば、社内の承認システムで申請書が提出された場合、まず直属の上司が確認し、その後に部門長、さらに必要であれば人事やCTOといった具合に処理が進んでいきます。ある段階で却下されれば、以降の処理は行われません。

基本的な実装

まずは、申請情報を表すクラスを定義します:

public class ApplicationRequest {
    private String title;
    private String requester;

    public ApplicationRequest(String title, String requester) {
        this.title = title;
        this.requester = requester;
    }

    public String getTitle() { return title; }
    public String getRequester() { return requester; }
}

次に、各承認ステップを表現するインターフェースを作ります:

public interface Approver {
    void process(ApplicationRequest request);
}

具体的な承認者クラスを実装します:

public class TeamLeadApprover implements Approver {
    public void process(ApplicationRequest request) {
        System.out.println("Team Lead approved: " + request.getTitle() + " by " + request.getRequester());
    }
}

public class DepartmentHeadApprover implements Approver {
    public void process(ApplicationRequest request) {
        System.out.println("Department Head approved: " + request.getTitle() + " by " + request.getRequester());
    }
}

public class HRApprover implements Approver {
    public void process(ApplicationRequest request) {
        System.out.println("HR approved: " + request.getTitle() + " by " + request.getRequester());
    }
}

これらの処理をまとめるチェーンクラスを用意します:

import java.util.*;

public class ApprovalChain {
    private List<Approver> approvers = new ArrayList<>();

    public void addApprover(Approver approver) {
        approvers.add(approver);
    }

    public void execute(ApplicationRequest request) {
        for (Approver approver : approvers) {
            approver.process(request);
        }
        System.out.println("Processing completed for: " + request.getTitle());
    }
}

テストコード:

public class ChainTest {
    public static void main(String[] args) {
        ApprovalChain chain = new ApprovalChain();
        chain.addApprover(new TeamLeadApprover());
        chain.addApprover(new DepartmentHeadApprover());
        chain.addApprover(new HRApprover());

        chain.execute(new ApplicationRequest("Salary Increase Request", "Qin Huai"));
    }
}

条件付き終了の実装

すべての承認者が常に承認するとは限りません。たとえば、途中で却下されるケースに対応するために、処理結果をbooleanで返すように変更します:

public interface Approver {
    boolean process(ApplicationRequest request);
}

各承認クラスも修正します:

public class TeamLeadApprover implements Approver {
    public boolean process(ApplicationRequest request) {
        System.out.println("Team Lead approved: " + request.getTitle());
        return true;
    }
}

public class DepartmentHeadApprover implements Approver {
    public boolean process(ApplicationRequest request) {
        System.out.println("Department Head approved: " + request.getTitle());
        return true;
    }
}

public class HRApprover implements Approver {
    public boolean process(ApplicationRequest request) {
        System.out.println("HR rejected: " + request.getTitle());
        return false;
    }
}

チェーンの実行ロジックも更新します:

public class ConditionalApprovalChain {
    private List<Approver> approvers = new ArrayList<>();

    public void add(Approver approver) {
        approvers.add(approver);
    }

    public void run(ApplicationRequest request) {
        for (Approver a : approvers) {
            if (!a.process(request)) {
                System.out.println("Request denied: " + request.getTitle());
                return;
            }
        }
        System.out.println("All approvals granted.");
    }
}

次のハンドラを持つ形式

別の実装として、各ハンドラが次のハンドラへの参照を持つ方法があります。これにより、処理の流れをより明示的に制御できます:

public abstract class BaseApprover {
    protected BaseApprover next;

    public void setNext(BaseApprover next) {
        this.next = next;
    }

    public abstract boolean handle(ApplicationRequest req);

    protected boolean forward(ApplicationRequest req) {
        if (next != null) {
            return next.handle(req);
        }
        return true;
    }
}

継承したクラスでの実装例:

public class LeadApprover extends BaseApprover {
    public boolean handle(ApplicationRequest req) {
        System.out.println("Lead processed: " + req.getTitle());
        return forward(req);
    }
}

public class ManagerApprover extends BaseApprover {
    public boolean handle(ApplicationRequest req) {
        System.out.println("Manager processed: " + req.getTitle());
        return forward(req);
    }
}

利用例:

BaseApprover lead = new LeadApprover();
BaseApprover mgr = new ManagerApprover();
lead.setNext(mgr);
lead.handle(new ApplicationRequest("Leave Application", "Alice"));

フレームワークとの統合

Spring Frameworkでは、@Orderアノテーションを使用してハンドラの実行順序を制御したり、コンポーネントスキャン機能を使って自動的にチェーンを構築することが可能です:

@Component
@Order(1)
public class FirstValidator implements Validator {...}

@Component
@Order(2)
public class SecondValidator implements Validator {...}

利点と注意点

  • 利点
    • オブジェクト間の結合度が低くなる
    • 新しい処理ステップの追加が容易
    • 処理順序の柔軟な変更が可能
  • 注意点
    • チェーンが長くなるとパフォーマンスに影響が出る可能性がある
    • どこかのステップで処理が途切れると、全体の処理が完了しない

責任チェーンパターンは、複数の処理ステップを持つ業務フローやフィルタリング処理など、順次的なデータ処理が必要な場面で非常に有用です。

タグ: Java Design Patterns Chain of Responsibility Spring Framework

5月22日 12:39 投稿