はじめに
GoFのデザインパターンを紹介している『増補改訂版 Java言語で学ぶデザインパターン入門』を読んで、学んだ内容についてまとめます。
Chain of Responsibilityパターン
Chain of Responsibilityとは
日本語に訳すと「責任の連鎖」を意味します。
複数のオブジェクトを鎖のように繋ぎ、各オブジェクトを順番に渡り歩いて目的のオブジェクトを決定するようなパターンのことをChain of Responsibilityパターンと言います。
イメージとしては、仕事の書類を提出しようとした際に、人事部のAさん→経理部のBさん→総務部のCさんといった風に提出先がドンドンたらい回しにされていくといったことが分かりやすいかと思います。
このパターンを適用することで、「処理の要求をする側」と「処理を行なう側」の結びつきを弱めることができるため、それぞれを部品化することができます。
逆にこのパターンを使用しないと「処理を要求する側」がこの処理はこのオブジェクトに要求する必要があるといった情報を中央集権的に持つ必要があり、そうすると部品としての独立性がなくなるため、再利用が難しくなります。
登場人物
Chain of Responsibilityパターン使用するのは以下のクラス図に登場するクラスです。
抽象クラス
-
Handler
要求を処理するクラスの雛形となるクラスです。フィールドに次のオブジェクトにたらい回しにする先nxet
を持ち、メソッドに次のオブジェクトにたらい回すrequest
を持ちます。特徴はたらい回しにする先もこのHandlerクラスを継承しているという点です。このようにする事で次々と連鎖を繋げることが可能になります。
実装クラス
-
ConcreteHandler
Handler
クラスの実装クラスです。要求に対して具体的な処理を行ないます。 -
Client
ConcreteHandlerクラスに要求を出すクラスです。特に難しい点はありません。
具体例
その他クラス
- Troubleクラス
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クラス
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クラス
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クラス
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クラス
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クラス
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クラス
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パターンに関して学びました。
以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。
また、他のデザインパターンに関しては以下でまとめていますので、こちらも参考にどうぞ。