mohuneko’s blog

かんばる駆け出しエンジニアのブログです

【Java】Chain of Responsibility パターン【デザインパターン】

駆け出しエンジニアがデザインパターンをもくもく勉強します

 こんな本で勉強しています🌟

目次

Chain of Responsibilityパターンについて

Chain of Responsibilityパターンとは

  • 複数のオブジェクトを鎖で繋いでおき、そのオブジェクトの鎖を順次渡り歩いて目的のオブジェクトを決定するパターンです。
  • Chain of Responsibilityは、責任の連鎖という意味で、"たらい回し構造" というイメージです
  • ある要求が発生した時に、その要求を処理するオブジェクトをダイレクトに決定できない時に、委譲によって、"たらい回し"にします
  • こうすることで、要求を行う側と処理を行う側の結合度を下げ、部品として独立性を高めることができます

サンプルプログラム

  • Chain of Responsibilityパターンを使って、発生したトラブルを、どこかのサポートセンターが処理する例を取り上げます
  • 各クラスの役割は以下のようになっています
名前 役割
Trouble 発生したトラブルを表すクラス
Support (Handler) トラブルを解決する抽象クラス
NoSupport (ConcreteHandler) トラブルを解決する具象クラス
常に処理しない
LimitSupport (ConcreteHandler) トラブルを解決する具象クラス
指定した番号未満のトラブルを解決できる
OddSupport(ConcreteHandler) トラブルを解決する具象クラス
奇数番号のトラブルを解決できる
SpecialSupport (ConcreteHandler) トラブルを解決する具象クラス
特定番号のトラブルを解決できる
Main (Client) Supportの連鎖を作り、トラブルを発生させる動作確認用のクラス

f:id:mohuNeko:20201230122809p:plain

Troubleクラス

  • numberはトラブル番号で、getNumberでトラブル番号を得ます
public class Trouble {
    private int number;             // トラブル番号
    public Trouble(int number) {    // トラブルの生成
        this.number = number;
    }
    public int getNumber() {        // トラブル番号を得る
        return number;
    }
    public String toString() {      // トラブルの文字列表現
        return "[Trouble " + number + "]";
    }
}

Supportクラス

  • nextフィールドはたらい回しの先を表し、setNextメソッドで次のたらい回し先を決定します
  • resolveメソッドはサブクラスで実装し、trueなら要求が処理され、falseは処理されなかったことを表します
  • supportメソッドでは、resolveを呼びだし、戻り値がfalseなら次の人にたらい回しにします
    • もし次のたらい回し先がなかった場合、自分が最後の連鎖となり、処理できなかったことを表示します
  • supportメソッドは抽象メソッドresolveを使ったTemplate Methodパターンになっています
public abstract class Support {
    private String name;                    // このトラブル解決者の名前
    private Support next;                 // たらい回しの先
    public Support(String name) {           // トラブル解決者の生成
        this.name = name;
    }
    public Support setNext(Support 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);
        }
    }
    public String toString() {              // 文字列表現
        return "[" + name + "]";
    }
    protected abstract boolean resolve(Trouble trouble); // 解決用メソッド
    protected void done(Trouble trouble) {  // 解決
        System.out.println(trouble + " is resolved by " + this + ".");
    }
    protected void fail(Trouble trouble) {  // 未解決
        System.out.println(trouble + " cannot be resolved.");
    }
}

NoSupportクラス

  • Supportクラスのサブクラスで、resolveメソッドを実装します
  • resolveメソッドでは、常にfalseを返します
public class NoSupport extends Support {
    public NoSupport(String name) {
        super(name);
    }
    protected boolean resolve(Trouble trouble) {     // 解決用メソッド
        return false; // 自分は何も処理しない
    }
}

LimitSupportクラス

  • Supportクラスのサブクラスで、resolveメソッドを実装します
  • resolveメソッドでは、limitで指定した番号未満のトラブルを処理します
public class LimitSupport extends Support {
    private int limit;                              // この番号未満なら解決できる
    public LimitSupport(String name, int limit) {   // コンストラクタ
        super(name);
        this.limit = limit;
    }
    protected boolean resolve(Trouble trouble) {         // 解決用メソッド
        if (trouble.getNumber() < limit) {
            return true;
        } else {
            return false;
        }
    }
}

OddSupportクラス

  • resolveメソッドでは、奇数番号のトラブルを処理します
public class OddSupport extends Support {
    public OddSupport(String name) {                // コンストラクタ
        super(name);
    }
    protected boolean resolve(Trouble trouble) {    // 解決用メソッド
        if (trouble.getNumber() % 2 == 1) {
            return true;
        } else {
            return false;
        }
    }
}

SpecialSupportクラス

  • resolveメソッドでは、指定番号のトラブルのみ処理します
public class SpecialSupport extends Support {
    private int number;        // ここで指定した番号だけ解決できる
    public SpecialSupport(String name, int number) {    // コンストラクタ
        super(name);
        this.number = number;
    }
    protected boolean resolve(Trouble trouble) {     // 解決用メソッド
        if (trouble.getNumber() == number) {
            return true;
        } else {
            return false;
        }
    }
}

Mainクラスで動作確認

  • Supportクラス型の変数である"解決者"に代入されたインスタンスで、解決者がトラブルを処理します
    • 例えば、AliceはNoSupportのインスタンスが割り当てられているので、何も解決しません
  • setNextメソッドで解決者を一列に並べ、トラブルを一個ずつ作成します
  • はじめに"alice"に渡し、誰が問題を解決したか表示します
public class Main {
    public static void main(String[] args) {
        Support alice   = new NoSupport("Alice");
        Support bob     = new LimitSupport("Bob", 100);
        Support charlie = new SpecialSupport("Charlie", 429);
        Support diana   = new LimitSupport("Diana", 200);
        Support elmo    = new OddSupport("Elmo");
        Support fred    = new LimitSupport("Fred", 300);
        // 連鎖の形成
        alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);
        // さまざまなトラブル発生
        for (int i = 0; i < 500; i += 33) { //33ずつ番号を増加
            alice.support(new Trouble(i));
        }
    }
}
  • 実行結果
[Trouble 0] is resolved by [Bob].
[Trouble 33] is resolved by [Bob].
[Trouble 66] is resolved by [Bob].
[Trouble 99] is resolved by [Bob].
[Trouble 132] is resolved by [Diana].
[Trouble 165] is resolved by [Diana].
[Trouble 198] is resolved by [Diana].
[Trouble 231] is resolved by [Elmo].
[Trouble 264] is resolved by [Fred].
[Trouble 297] is resolved by [Elmo].
[Trouble 330] cannot be resolved.
[Trouble 363] is resolved by [Elmo].
[Trouble 396] cannot be resolved.
[Trouble 429] is resolved by [Charlie].
[Trouble 462] cannot be resolved.
[Trouble 495] is resolved by [Elmo].

Chain of Responsibilityパターンのメリット

  • Chain of Responsibilityパターンのポイントは、"要求を出す人(Main)" と "要求を処理する人(Handler)" を緩やかに結びつけることです
  • "要求を出す人(Main)"が、最初の人に要求を出すと、連鎖の中をその要求が流れていき、適切な人が要求を処理します
    • Chain of Responsibilityパターンを使わないと、"この要求はこの人が処理する" という知識を定義する必要があります
    • その知識を"要求を出す人(Main)"に持たせてしまうと、部品としての独立性が損なわれてしまいます
    • もしくは、ConcreteHandlerが仕事の振り分け条件を定義する必要があります
  • 委譲によって、たらい回しをすることで、状況に合わせて ConcreteHandlerの順番を変更することができます
    • もし、トラブルと解決者が固定されていたら、プログラム実行中に、動的に解決者を変更することができません
  • たらい回しにすることで、各オブジェクトが自分の処理に集中できます

Chain of Responsibilityパターンのデメリット

  • Chain of Responsibilityパターンは柔軟性が高くなりますが、たらい回しにする分、処理が遅くなり易いです

今日のポイント

  • 複数のオブジェクトを鎖で繋いでおき、そのオブジェクトの鎖を順次渡り歩いて目的のオブジェクトを決定するパターンです。
  • ある要求が発生した時に、その要求を処理するオブジェクトをダイレクトに決定できない時に、委譲によって、"たらい回し"にします
  • こうすることで、要求を行う側と処理を行う側の結合度を下げ、部品として独立性を高めることができます
  • たらい回しにすることで、各オブジェクトが自分の処理に集中できます

 本日もお疲れ様です😊