技術共有

設計パターン 責任連鎖パターン

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

1.コンセプト

責任の連鎖パターン: リクエストの送信者と受信者の結合を避け、複数のオブジェクトにリクエストを受信する機会を与え、これらのオブジェクトをチェーンに接続し、処理するオブジェクトが見つかるまでこのチェーンに沿ってリクエストを渡します。責任連鎖モデルは、オブジェクトの動作パターン

2. 構造

責任連鎖パターン構造の中核は、抽象プロセッサ
ここに画像の説明を挿入します

図からわかるように、責任連鎖パターン構造図には次の 2 つの役割が含まれています。
(1) ハンドラー (抽象ハンドラー): リクエストを処理するためのインターフェースを定義し、一般に抽象クラスとして設計されます。具象ハンドラーが異なればリクエストの処理方法も異なるため、抽象リクエスト処理メソッドが具象ハンドラー内で定義されます。各プロセッサの配下は依然としてプロセッサであるため、その配下として抽象プロセッサ内に抽象プロセッサ型のオブジェクト(構造図における後継)が定義される。引用 。この参照を通じて、ハンドラーをチェーンにリンクできます。
(2) ConcreteHandler (具体的なハンドラー): 抽象ハンドラーのサブクラスであり、ユーザーのリクエストを処理できます。抽象プロセッサで定義された抽象リクエスト処理メソッドは具象プロセッサ クラスに実装され、チェーン内の次のオブジェクトにアクセスして目的を達成できます。転送リクエスト効果。
Chain of Responsibility パターンでは、各オブジェクトの子孫への参照が接続されてチェーンを形成します。リクエストは、チェーン内のどのオブジェクトが最終的にリクエストを処理するかまでこのチェーンに渡されます。これにより、システムはクライアントに影響を与えることなくチェーンを動的に再編成し、責任を割り当てることができます。

3.代表的なコード

責任連鎖パターンの中核は、抽象ハンドラー クラスの設計にあります。抽象ハンドラー クラスの一般的なコードは次のとおりです。

abstract class Handler {
    //维持对下家的引用
    protected Handler successor;
    
    public void setSuccessor(Handler successor) {
        this.successor = successor;    
    }
    
    public abstract void handleRequest(String request);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上記のコードでは、抽象プロセッサは、リクエストを次のプロセッサに転送するために、次のプロセッサへの参照オブジェクトを定義します。このオブジェクトのアクセサーは protected に設定でき、そのサブクラスで使用できます。

具象ハンドラーは抽象ハンドラーのサブクラスであり、次の 2 つの主要な機能があります。

  • リクエストを処理するために、さまざまな特定のプロセッサがさまざまな形式で抽象リクエスト処理メソッド handleRequest() を実装します。
  • リクエストを転送する リクエストが現在のプロセッサの処理範囲を超える場合、リクエストを次のプロセッサに転送できます。

特定のハンドラー クラスの一般的なコードは次のとおりです。

class ConcreteHandler extends Handler {
    public void handleRequest(String request) {
        if (请求满足条件) {
            //处理请求        
        } 
        else {
            this.successor.handleRequest(request);  //转发请求
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4. 事例1:発注承認システム

ある発注書の承認システムは階層的になっており、購入金額に応じて異なるレベルの監督者によって承認されます。

取締役は50,000元未満の発注書を承認でき、副会長は[5,100,000元]の発注書を承認でき、取締役会会長は[10,500,000元]の発注書を承認でき、500,000元の発注書を承認できます。以上については、取締役会で議論して決定する必要があります。
ここに画像の説明を挿入します

各役職の承認者には部下(取締役会を除く)がおり、その行動は共通しているため、後任者に承認を転送することになります。したがって、承認者クラスを抽象プロセッサとして設計します。

//审批者类: 抽象处理者
abstract class Approver {
    protected Approver successor;  //定义后继对象
    protected String name;  //审批者姓名

    public Approver(String name) {
        this.name = name;
    }

    //设置后继者
    public void setSuccessor(Approver successor) {
        this.successor = successor;
    }

    public abstract void processRequest(PurchaseRequest request);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

次に、各位置は、特定のプロセッサとして、抽象プロセッサを実装する必要があります。

//主任: 具体处理者
class Director extends Approver{
    public Director(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < 50000) {
            System.out.println(
                    MessageFormat.format("主任 {0} 审批采购单:{1}, 金额:{2}元, 采购目的:{3}。",
                            this.name, request.getNumber(), request.getAmount(), request.getPurpose())
            );
        } else {
            this.successor.processRequest(request);  //转发请求
        }
    }
}

//副董事长:具体处理类
class VicePresident extends Approver{
    public VicePresident(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < 100000) {
            System.out.println(
                    MessageFormat.format("副董事长 {0} 审批采购单:{1}, 金额:{2}元, 采购目的:{3}。",
                            this.name, request.getNumber(), request.getAmount(), request.getPurpose())
            );
        } else {
            this.successor.processRequest(request);
        }
    }
}

//董事长类:具体处理者
class President extends Approver{
    public President(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < 500000) {
            System.out.println(
                    MessageFormat.format("董事长 {0} 审批采购单:{1}, 金额:{2}元, 采购目的:{3}。",
                            this.name, request.getNumber(), request.getAmount(), request.getPurpose())
            );
        } else {
            this.successor.processRequest(request);
        }
    }
}

//董事会类:具体处理者
class Congress extends Approver{
    public Congress(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest request) {
        System.out.println(
                MessageFormat.format("召开董事会 审批采购单:{0}, 金额:{1}元, 采购目的:{2}。",
                        request.getNumber(), request.getAmount(), request.getPurpose())
        );
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

承認が必要なターゲットとして別の発注書カテゴリを定義します。

//采购单: 请求类
class PurchaseRequest {
    private double amount;  //采购金额
    private int number;  //采购单编号
    private String purpose;  //采购目的

    public PurchaseRequest(double amount, int number, String purpose) {
        this.amount = amount;
        this.number = number;
        this.purpose = purpose;
    }

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public String getPurpose() {
        return purpose;
    }

    public void setPurpose(String purpose) {
        this.purpose = purpose;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

クライアント テスト コードを作成します。

class Client {
    public static void main(String[] args) {
        Approver kangXi, yongZheng, qianLong, hanLinYuan;
        kangXi = new Director("康熙");
        yongZheng = new VicePresident("雍正");
        qianLong = new VicePresident("乾隆");
        hanLinYuan = new Congress("翰林院");

        //创建职责链
        kangXi.setSuccessor(yongZheng);
        yongZheng.setSuccessor(qianLong);
        qianLong.setSuccessor(hanLinYuan);

        //创建采购单
        PurchaseRequest pr1 = new PurchaseRequest(45000, 10001, "购买刀");
        kangXi.processRequest(pr1);

        PurchaseRequest pr2 = new PurchaseRequest(60000, 10002, "购买枪");
        kangXi.processRequest(pr2);

        PurchaseRequest pr3 = new PurchaseRequest(160000, 10003, "购买火炮");
        kangXi.processRequest(pr3);

        PurchaseRequest pr4 = new PurchaseRequest(800000, 10004, "购买军舰");
        kangXi.processRequest(pr4);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

プログラムをコンパイルして実行すると、出力は次のようになります。

主任 康熙 审批采购单:10,001, 金额:45,000元, 采购目的:购买刀。
副董事长 雍正 审批采购单:10,002, 金额:60,000元, 采购目的:购买枪。
董事长 乾隆 审批采购单:10,003, 金额:160,000元, 采购目的:购买火炮。
召开董事会 审批采购单:10,004, 金额:800,000元, 采购目的:购买军舰。
  • 1
  • 2
  • 3
  • 4

5. 事例分析 2: クラスタスクの処理

上記とは少し異なるケースを設計してみましょう。これにより、責任連鎖モデルとそれが適用できるシナリオについての理解をさらに広げることができます。

  • 最初の違い: ハンドラーのリクエスト メソッドには戻り値があり、ホストに情報をフィードバックできます。
  • 2 番目の違い: 「発注承認システム」ケースは最初にリクエストを処理してからリクエストを転送しますが、「クラス タスク処理」ケースは最初にリクエストを転送してからリクエストを処理します。

学校は、処理するためにクラスにいくつかのタスクを割り当てます。これらのタイプには、1、2、3、4 などがあります。クラスの教師は 1、2、3 の 3 種類のタスクを処理でき、モニターは 2 種類のタスクを処理できます。 1 つ、2 つのタスク、学習委員会はこのタイプのタスクの 1 つを処理できます。クラスの先生がタスクを受け取ると、まず班長に引き継ぎ、次のクラスで処理できない場合は、クラスの先生が自分でタスクを処理します。次のクラスが処理できない場合は、クラスの教師が処理します。学校委員会がタスクを受け取った場合、分隊長は自分で処理します。そして、それに対処できないときは、上向きのフィードバックを与えます。

Handler クラスは、抽象プロセッサとして以下のように設計されています。
あなたとあなたの部下は必ずしも特定のクラスのタスクを処理できるとは限らず、上向きのフィードバックがあるため、プロセッサのリクエスト メソッドには、タスクを処理できるかどうかを上司に伝えるためのブール値の戻り値が必要です。

abstract class Handler {
    protected Handler successor;  //定义后继对象

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

    public abstract boolean handleRequest(String taskName);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

以下では、学級担任、班長、学習委員がそれぞれ特定の担当者として設計されています。
デフォルトでは、クラスの教師が最初にモニターにタスクを与え、モニターはデフォルトで学習委員会のメンバーにタスクを与えます。学習委員会のメンバーは 1 種類のタスクのみを処理でき、処理できない場合はそのタスクを処理します。 false を返します。
分隊リーダーが受け取ったフィードバックが偽の場合、彼は自分でそれを処理します。彼が処理できない場合は、1 つまたは 2 つのタイプのタスクしか処理できません。
校長が受け取ったフィードバックが偽の場合、彼は 1 つ、2 つ、または 3 つの種類のタスクしか処理できません。

//班主任
class HeadTeacher extends Handler{
    @Override
    public boolean handleRequest(String taskName) {
        boolean handled = successor.handleRequest(taskName);
        if (handled) {
            return true;
        }
        if (taskName.equals("one") || taskName.equals("two") || taskName.equals("three")) {
            System.out.println("班主任处理了该事务");
            return true;
        }

        return false;
    }
}

//班长
class Monitor extends Handler{
    @Override
    public boolean handleRequest(String taskName) {
        boolean handled = successor.handleRequest(taskName);
        if (handled) {
            return true;
        }
        if (taskName.equals("one") || taskName.equals("two")) {
            System.out.println("班长处理了该事务");
            return true;
        }

        return false;
    }
}

//学习委员
class StudyCommissary extends Handler{
    @Override
    public boolean handleRequest(String taskName) {
        boolean handled;
        if (successor == null) {  //注意学习委员可能没有下家,所以这里判一下是否为空
            handled = false;
        } else {
            handled = successor.handleRequest(taskName);
        }

        if (handled) {
            return true;
        }
        if (taskName.equals("one")) {
            System.out.println("学习委员处理了该事务");
            return true;
        }

        return false;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

クライアント テスト コードを作成します。
役職ごとに分院を設置しますが、検討委員会の分院はありませんのでご注意ください。
また、校長が対応できないタスクがある場合は、ログを 1 行印刷して説明します。

public class SchoolClient {
    public static void main(String[] args) {
        Handler headTeacher, monitor, studyCommissary;
        headTeacher = new HeadTeacher();
        monitor = new Monitor();
        studyCommissary = new StudyCommissary();

        headTeacher.setSuccessor(monitor);
        monitor.setSuccessor(studyCommissary);
        studyCommissary.setSuccessor(null);  //没有下一职责人

        startRequest(headTeacher, "one");
        startRequest(headTeacher, "two");
        startRequest(headTeacher, "three");
        startRequest(headTeacher, "four");
    }

    private static void startRequest(Handler headTeacher, String taskName) {
        if (! headTeacher.handleRequest(taskName)) {
            System.out.println("该班级处理不了此类任务!");
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

プログラムをコンパイルして実行すると、出力は次のようになります。

学习委员处理了该事务
班长处理了该事务
班主任处理了该事务
该班级处理不了此类任务!
  • 1
  • 2
  • 3
  • 4

6. 適用可能なシナリオ

責任連鎖モデルは、次のような状況で考慮されることがあります。

  • 複数のオブジェクトが同じリクエストを処理できます。リクエストを処理する特定のオブジェクトは実行時に決定されます。クライアントは、リクエストが誰によって処理されるか、どのように処理されるかを気にすることなく、リクエストをチェーンに送信するだけで済みます。
  • 受信者を明示的に指定せずに、複数のオブジェクトの 1 つにリクエストを送信します。
  • リクエストを処理するためにオブジェクトのセットを動的に指定できます。クライアントは、要求を処理する責任のチェーンを動的に作成でき、チェーン内のプロセッサ間の順序を変更することもできます。


参考書:
「デザインパターンの芸術」 - リウ・ウェイ