Help us understand the problem. What is going on with this article?

JavaでChain of Responsibilityパターン

More than 1 year has passed since last update.

はじめに

GoFのデザインパターンを紹介している『増補改訂版 Java言語で学ぶデザインパターン入門』を読んで、学んだ内容についてまとめます。

Chain of Responsibilityパターン

Chain of Responsibilityとは

日本語に訳すと「責任の連鎖」を意味します。
複数のオブジェクトを鎖のように繋ぎ、各オブジェクトを順番に渡り歩いて目的のオブジェクトを決定するようなパターンのことをChain of Responsibilityパターンと言います。
イメージとしては、仕事の書類を提出しようとした際に、人事部のAさん→経理部のBさん→総務部のCさんといった風に提出先がドンドンたらい回しにされていくといったことが分かりやすいかと思います。
このパターンを適用することで、「処理の要求をする側」と「処理を行なう側」の結びつきを弱めることができるため、それぞれを部品化することができます。
逆にこのパターンを使用しないと「処理を要求する側」がこの処理はこのオブジェクトに要求する必要があるといった情報を中央集権的に持つ必要があり、そうすると部品としての独立性がなくなるため、再利用が難しくなります。

登場人物

Chain of Responsibilityパターン使用するのは以下のクラス図に登場するクラスです。
image.png

抽象クラス

  • Handler
    要求を処理するクラスの雛形となるクラスです。フィールドに次のオブジェクトにたらい回しにする先nxetを持ち、メソッドに次のオブジェクトにたらい回すrequestを持ちます。特徴はたらい回しにする先もこのHandlerクラスを継承しているという点です。このようにする事で次々と連鎖を繋げることが可能になります。

実装クラス

  • ConcreteHandler
    Handlerクラスの実装クラスです。要求に対して具体的な処理を行ないます。

  • Client
    ConcreteHandlerクラスに要求を出すクラスです。特に難しい点はありません。

具体例

具体例として、以下のクラスをもとに説明します。
image.png

その他クラス

  • Troubleクラス
Trouble.java
public class Trouble {

    private int number; // トラブル番号
    private String content; // トラブル内容
    private int level; // トラブルレベル

    public Trouble(int number, String content, int level) { // コンストラクタ トラブルの生成
        this.number = number;
        this.content = content;
        this.level = level;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int revel) {
        this.level = revel;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public int getNumber() {
        return number;
    }

    public String toString() { // トラブルの文字列表現
        return "[#" + number + " " + content + "(" + level + ")" + "]";
    }

}

Troubleクラスは発生したトラブルを表すクラスです。
フィールドにトラブル番号、トラブル内容、トラブルレベルを持ちます。
特段難しい点はありません。

抽象クラス

  • Responsibleクラス
Responsible.java
public abstract class Responsible {

    private String name; // このトラブル解決者の名前
    private Responsible next; // たらい回しの先

    public Responsible(String name) { // コンストラクタ トラブル解決者の生成
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public Responsible setNext(Responsible next) { // たらい回しの先を設定
        this.next = next;
        return next;
    }

    public void support(Trouble trouble) { // トラブル解決の手順
        if (resolve(trouble)) {// トラブルが解決できた場合
            done(trouble);
        } else if (next != null) {// トラブルが解決できなかったが次のたらい回しの先が設定されている場合
            next.support(trouble);
        } else {// トラブルが解決できず、次のたらい回しの先も設定されていない場合
            fail(trouble);
        }
    }

    protected abstract boolean resolve(Trouble trouble); // 解決用メソッド

    protected void done(Trouble trouble) { // 解決
        System.out.println(trouble + " は  " + getName() + "が解決しました。");
    }

    protected void fail(Trouble trouble) { // 未解決
        System.out.println("【警告】 " + trouble + " は誰も解決できませんでした。");
    }

}

Responsibleクラスはトラブルを解決する連鎖を作るためのクラスです。
ポイントは以下の4点です。

 1.nextフィールドに次にたらい回しする先を持つこと。
 2.setNextメソッドでたらい回す先の設定と返却を行うこと。
 3.supportメソッドでトラブル解決のための手順を定義していること。
 4.resolveメソッドを抽象メソッドとして定義し、サブクラスで実装するようにしていること。

ポイント1,2,3について補足します。
ポイント1に関して、設定する次のたらい回し先はResponsibleクラスのサブクラス(実装クラス)であるため、ResponsibleのサブクラスA→ResponsibleのサブクラスB→ResponsibleのサブクラスC...といったように責任の連鎖を作成することができます。
次にポイント2に関して、たらい回す先の設定はsetNextメソッドで行います。この時に設定したたらい回し先を返却しているので、hoge.setNext(foo).setNext(bar)....といったメソッドの実行が可能です。
最後にポイント3に関して、supportメソッドでは解決すべきトラブルを引数に持ち、抽象メソッドresolveを呼び出し、戻り値がtrue(解決)であった場合には、doneメソッドでトラブルが解決できたことを表示します。また戻り値がfalse(未解決)で次のたらい回し先が設定されている場合には、次のたらい回し先のsupportメソッドを呼び出すことで、次のたらい回し先にトラブルの解決を委ねます。しかし、戻り値がfalse(未解決)でなおかつ次のたらい回し先が設定されていない場合には、自身が連鎖の最後であり、どのオブジェクトでも処理ができなかったことを意味するため、failメソッドでトラブルが解決できなかったことを表示します。

実装クラス

  • Employeeクラス
Employee.java
public class Employee extends Responsible {

    private int limitLevel = 1; // このレベル以下なら解決できる

    public Employee(String name) { // コンストラクタ
        super(name);
    }

    @Override
    protected boolean resolve(Trouble trouble) { // 解決用メソッド
        if (trouble.getLevel() <= limitLevel) {
            return true;
        } else {
            return false;
        }
    }

}
  • Chiefクラス
Chief.java
public class Chief extends Responsible {

    private int limitLevel = 5; // このレベル以下なら解決できる

    public Chief(String name) { // コンストラクタ
        super(name);
    }

    @Override
    protected boolean resolve(Trouble trouble) { // 解決用メソッド
        if (trouble.getLevel() <= limitLevel) {
            return true;
        } else {
            return false;
        }
    }

}
  • Managerクラス
Manager.java
public class Manager extends Responsible {

    private int limitLevel = 10; // このレベル以下なら解決できる

    public Manager(String name) { // コンストラクタ
        super(name);
    }

    @Override
    protected boolean resolve(Trouble trouble) { // 解決用メソッド
        if (trouble.getLevel() <= limitLevel) {
            return true;
        } else {
            return false;
        }
    }

}
  • Presidentクラス
President.java
public class President extends Responsible {

    private int limitLevel = 20; // このレベル以下なら解決できる

    public President(String name) { // コンストラクタ
        super(name);
    }

    @Override
    protected boolean resolve(Trouble trouble) { // 解決用メソッド
        if (trouble.getLevel() <= limitLevel) {
            return true;
        } else {
            return false;
        }
    }

}

Employeeクラス、Chiefクラス、Managerクラス、PresidentクラスはResponsibleクラスの実装クラスです。
それぞれ自身が解決できるトラブルレベルの上限値を持っており、この上限値を超えない範囲のトラブルを解決することができるように、抽象メソッドのresolveメソッドをオーバーライドしています。

実行クラス

  • Mainクラス
Main.java
public class Main {

    public static void main(String[] args) {

        // トラブル解決者の作成
        Responsible employee = new Employee("A社員");
        Responsible chief = new Chief("B課長");
        Responsible manager = new Manager("C部長");
        Responsible president = new President("D社長");

        // 連鎖の形成
        employee.setNext(chief).setNext(manager).setNext(president);

        // さまざまなトラブル発生
        employee.support(new Trouble(1, "顧客クレーム対応", 1));
        employee.support(new Trouble(2, "海外出張対応", 10));
        employee.support(new Trouble(3, "世界平和", 100));
        employee.support(new Trouble(4, "見積作成対応", 5));
        employee.support(new Trouble(5, "経営方針策定対応", 20));

    }

}

まずトラブル解決者の作成を行います。
次に各オブジェクトに対してsetNextメソッドを呼び出していくことで連鎖の形成を行います。
最後に解決したいトラブルを作成し、employeeオブジェクトのsupportメソッドの引数として渡しています。

実行結果

Main.javaを実行した結果は以下になります。
各オブジェクトのトラブルレベルの範囲内で対応を行っていることが分かります。
また、誰も処理することができなかったトラブルに関しても解決できなかった旨の出力がされています。

実行結果
[#1 顧客クレーム対応(1)] は  A社員が解決しました。
[#2 海外出張対応(10)] は  C部長が解決しました。
【警告】 [#3 世界平和(100)] は誰も解決できませんでした。
[#4 見積作成対応(5)] は  B課長が解決しました。
[#5 経営方針策定対応(20)] は  D社長が解決しました。

メリット

Chain of Responsibilityパターンのメリットは以下になります。
 1.要求側と処理側の結びつきを弱めることにより、処理の独立性(部品化)が高まる。
 2.処理側のクラスには自身の責任の範囲内の処理のみを実装するので、簡潔に記述できる。

デメリット

逆にChain of Responsibilityパターンのデメリットは以下になります。
 1.連鎖を一からたどる必要があるため、処理が遅くなる。
  →要求と処理の関係が固定的で処理速度が重要視される場合には、このパターンは適用しない方がよい。

まとめ

責任をたらい回しにすることで処理するオブジェクトを決定するChain of Responsibilityパターンに関して学びました。
以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。

また、他のデザインパターンに関しては以下でまとめていますので、こちらも参考にどうぞ。

参考文献

mk777
SIer→Webに転職したエンジニア。学んだことなどをまとめていきます。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away