デザインパターン入門 | Chain of Responsibility(チェインオブレスポンシビリティ)パターン

「Chain of Responsibility(チェインオブレスポンシビリティ)」パターンは、「Chain(鎖)」「Responsibility(責任)」と、単語だけを並べてもあまり意味がわからないのですが、「責任を持つ対象」を「鎖状」に連結して、「責任」をそのインスタンスに連鎖させていくデザインパターンです。

今回は「仕事」と「プログラマ」を事例に「Chain of Responsibility(チェインオブレスポンシビリティ)」パターンについてご説明をしていきたいと思います。

「Responsibility(責任)」とは?

一口に「責任」と表現しても、どのようなことを対象にするのかによって、「責任」の種類は無数に存在しています。

何かを行う「義務・責務」と考えると、今回の事例では「仕事を行う」ことが、「Responsibility(責任)」と捉えることができます。

今回作るプログラムでは、「JobTask(仕事の課題)」を解決することが「Responsibility(責任)」となります。

そして、今回の「仕事の課題」とは「プログラミング」です。

それぞれの「仕事の課題」で必要な「プログラミングスキル」があらかじめ決められているため、「プログラマ」ごとに「対応可否」が異なります。

複数のプログラマによって、対応可能な「仕事の課題(プログラミング)」を解決していくことをテーマにサンプルコードを例示していきたいと思います。

「Chain of Responsibility(チェインオブレスポンシビリティ)」パターンのプログラム

「仕事の課題(ジョブ課題)」を表すクラスは「JobTask」クラス、その問題を解決するための抽象クラスである「Resolver」クラスと「Programmer」クラスを作っていきます。

チェインオブレスポンシビリティ1

「Programmer」クラスは、「Resolver」クラスを継承しています。

「Resolver」クラスは下記のようになります。

public abstract class Resolver {
	String name; // 名前
	Language language; // 対応できるプログラム言語(列挙型)
	Resolver nextResolver; // 自分が仕事の課題を解決できなかった場合の依頼先

	// コンストラクタ
	public Resolver(String name, Language language) {
		this.name = name;
		this.language = language;
	}

	// 自分が仕事の課題を解決できなかった場合の依頼先を設定
	public Resolver setNext(Resolver resolver) {
		this.nextResolver = resolver;
		return resolver;
	}

	protected abstract boolean executeJobTask(JobTask jt); // 仕事の実行
	protected abstract boolean resolve(JobTask jt); // 仕事の課題の解決
}

このクラスのフィールドには「Language列挙型」を利用していますが、その内容は下記のようになります。

public enum Language {
	CSharp,
	Java,
	COBOL,
	PHP,
	Ruby,
	Python
}

そして、「Programmer」クラスは下記のようになります。

public class Programmer extends Resolver {
	// コンストラクタ
	public Programmer(String name, Language language) {
		super(name, language);
	}

	// ジョブ課題解決の実行
	@Override
	protected boolean executeJobTask(JobTask jt) {
        if( resolve(jt) ) {
        	System.out.println(this.name + "は、「" + jt + "」を完了しました。");
        	return true;
        } else if( this.nextResolver != null ){
        	this.nextResolver.executeJobTask(jt);
        }
		return false;
	}

	// ジョブ課題の解決
	protected boolean resolve(JobTask jt) {
		if ( !jt.language.equals(this.language)){
			return false;
		}
		return true;
	}

	@Override
	public String toString() {
		return "名前:" + this.name + " プログラム言語:" + this.language;
	}
}

「仕事の課題(ジョブ課題)」を表す「JobTask」クラスは、下記のようになります。

public class JobTask {
	int jobNumber; // 仕事の番号
	Language language; // 仕事を行うために必要なプログラム言語
	
	// コンストラクタ
	public JobTask(int jobNumber, Language language) {
		this.jobNumber = jobNumber;
		this.language = language;
	}

	// 仕事の番号を取得
	public int getJobNumber() {
		return this.jobNumber;
	}

	@Override
	public String toString() {
		return "Job Number." + this.jobNumber + " need Language is " + this.language;
	}
}

これらのクラスを利用して処理を行う「Main」クラスは次のようになります。

public class Main {
	public static void main(String[] args) {
		Programmer satou = new Programmer("佐藤",  Language.CSharp);
		Programmer tanaka = new Programmer("田中",  Language.COBOL);
		Programmer suzuki = new Programmer("鈴木",  Language.Java);
		Programmer ozawa = new Programmer("小沢",  Language.Ruby);
		Programmer hamaoka = new Programmer("浜岡",  Language.PHP);
		Programmer yamada = new Programmer("山田",  Language.Python);

		System.out.println("======== プログラマ一覧 ========");
		System.out.println(satou);
		System.out.println(tanaka);
		System.out.println(suzuki);
		System.out.println(ozawa);
		System.out.println(hamaoka);
		System.out.println(yamada);

		Language[] languages = { Language.CSharp, Language.Java, Language.COBOL,
				                 Language.PHP, Language.Ruby, Language.Python };

		ArrayList jobTasks = new ArrayList();

		Random rand = new Random();

		for ( int i = 0; i < 10; i++ ) {
			jobTasks.add(new JobTask((i + 1), languages[rand.nextInt(languages.length)]));
		}

		System.out.println();

		// ジョブ課題の表示
		System.out.println("======== ジョブ一覧 ========");
		for ( JobTask jt : jobTasks ) {
			System.out.println(jt);
		}

		System.out.println();
		
		// 「ジョブ課題(責任)」の依頼先の「鎖」を作る
		satou.setNext(tanaka).setNext(suzuki).setNext(ozawa).setNext(hamaoka).setNext(yamada);

		// ジョブ課題解決の実行
		for ( JobTask jt : jobTasks ) {
			satou.executeJobTask(jt);
		}
	}
}

まず、さまざまなプログラミングスキルを持った「プログラマ」のインスタンスを生成し、「setNext」メソッドで自分が「仕事の課題(ジョブ課題)」を解決できなかった場合の「仕事の依頼先」を設定しています。

自分が身に付けているプログラム言語の「仕事の課題(ジョブ課題)」であれば、自分で解決をしますが、そうでなければ、次の依頼先に「仕事の課題(ジョブ課題)」を投げるという流れになっています。

チェインオブレスポンシビリティ2

誰がどのようなプログラミング言語を使えるのかはわからないため、順番に「仕事の課題」を次の人に投げていき、自分が使えるプログラム言語の課題であれば処理を行います。

ただ、毎回このような手順を踏むため、処理速度の低下を招くことも考えられます。

このプログラムの実行結果は下記のようになります。

======== プログラマ一覧 ========
名前:佐藤 プログラム言語:CSharp
名前:田中 プログラム言語:COBOL
名前:鈴木 プログラム言語:Java
名前:小沢 プログラム言語:Ruby
名前:浜岡 プログラム言語:PHP
名前:山田 プログラム言語:Python

======== ジョブ一覧 ========
Job Number.1 need Language is COBOL
Job Number.2 need Language is COBOL
Job Number.3 need Language is Python
Job Number.4 need Language is PHP
Job Number.5 need Language is PHP
Job Number.6 need Language is Python
Job Number.7 need Language is CSharp
Job Number.8 need Language is COBOL
Job Number.9 need Language is Ruby
Job Number.10 need Language is Java

田中は、「Job Number.1 need Language is COBOL」を完了しました。
田中は、「Job Number.2 need Language is COBOL」を完了しました。
山田は、「Job Number.3 need Language is Python」を完了しました。
浜岡は、「Job Number.4 need Language is PHP」を完了しました。
浜岡は、「Job Number.5 need Language is PHP」を完了しました。
山田は、「Job Number.6 need Language is Python」を完了しました。
佐藤は、「Job Number.7 need Language is CSharp」を完了しました。
田中は、「Job Number.8 need Language is COBOL」を完了しました。
小沢は、「Job Number.9 need Language is Ruby」を完了しました。
鈴木は、「Job Number.10 need Language is Java」を完了しました。

以上が、「Chain of Responsibility(チェインオブレスポンシビリティ)」パターンの利用例ですが、このようなパターンはたくさん考えられますので、必要な時に「Chain of Responsibility(チェインオブレスポンシビリティ)」パターンが使えるようになっておくことが大切ではないでしょうか。

HOMEへ