JavaにおけるChain of Responsibilityデザインパターン

責任連鎖デザインパターンは、行動のデザインパターンの一つです。

チェイン・オブ・レスポンシビリティデザインパターン

チェインオブレスポンシビリティパターンは、ソフトウェア設計においてクライアントからのリクエストが複数のオブジェクトのチェーンに渡され、それらを処理するために使用される。そして、チェーン内のオブジェクトは、リクエストを処理するのに誰が適しているか、そして次のオブジェクトにリクエストを送る必要があるかどうかを自身で決定する。

JDK内のChain of Responsibilityパターンの例

JDK内の責任連鎖パターンの例を見て、そのパターンの実生活の例を実装してみましょう。私たちは、try-catchブロックのコード内に複数のcatchブロックを持つことができることを知っています。ここでは、各catchブロックが特定の例外を処理するための処理装置のようなものです。そのため、tryブロックで例外が発生すると、最初のcatchブロックに送られて処理されます。もしcatchブロックがそれを処理できない場合、要求はチェーン内の次のオブジェクト、つまり次のcatchブロックに転送されます。もし最後のcatchブロックでも処理できない場合、例外はチェーンの外部にある呼び出しプログラムに投げられます。

Chain of Responsibilityデザインパターンの例

Chain of Responsibilityパターンの優れた例の1つは、ATMの現金支払い機です。ユーザーは支払い金額を入力し、機械は50ドル、20ドル、10ドルなどの通貨単位の紙幣を支払います。ユーザーが10の倍数でない金額を入力した場合、エラーが発生します。この解決策を実装するために、Chain of Responsibilityパターンを使用します。チェーンは、以下の画像と同じ順序でリクエストを処理します。なお、この解決策は単一のプログラムで簡単に実装できますが、複雑さが増し、解決策が強く結合されるという問題があります。したがって、50ドル、20ドル、10ドルの紙幣を支払うためのディスペンスシステムのチェーンを作成します。

Chain of Responsibility デザインパターン – 基本クラスとインタフェース

私たちは、金額をディスペンスに使われる通貨を保管するCurrencyクラスを作成することができます。Currency.javaはチェーンの実装によって使用されます。

package com.scdev.design.chainofresponsibility;

public class Currency {

	private int amount;
	
	public Currency(int amt){
		this.amount=amt;
	}
	
	public int getAmount(){
		return this.amount;
	}
}

基本のインターフェースには、チェーン内の次のプロセッサを定義するためのメソッドと、リクエストを処理するメソッドが必要です。私たちのATMディスペンスインターフェースは以下のようになります。DispenseChain.java

package com.scdev.design.chainofresponsibility;

public interface DispenseChain {

	void setNextChain(DispenseChain nextChain);
	
	void dispense(Currency cur);
}

Chain of Responsibilitiesパターン – チェーン実装

私たちは、DispenseChainインターフェースを実装し、dispenseメソッドの実装を提供する異なるプロセッサクラスを作成する必要があります。私たちは、システムを50ドル、20ドル、10ドルの3種類の通貨紙幣で動作するように開発しているため、3つの具体的な実装を作成します。Dollar50Dispenser.javaです。

package com.scdev.design.chainofresponsibility;

public class Dollar50Dispenser implements DispenseChain {

	private DispenseChain chain;
	
	@Override
	public void setNextChain(DispenseChain nextChain) {
		this.chain=nextChain;
	}

	@Override
	public void dispense(Currency cur) {
		if(cur.getAmount() >= 50){
			int num = cur.getAmount()/50;
			int remainder = cur.getAmount() % 50;
			System.out.println("Dispensing "+num+" 50$ note");
			if(remainder !=0) this.chain.dispense(new Currency(remainder));
		}else{
			this.chain.dispense(cur);
		}
	}

}

20ドルのディスペンサー.java

package com.scdev.design.chainofresponsibility;

public class Dollar20Dispenser implements DispenseChain{

	private DispenseChain chain;
	
	@Override
	public void setNextChain(DispenseChain nextChain) {
		this.chain=nextChain;
	}

	@Override
	public void dispense(Currency cur) {
		if(cur.getAmount() >= 20){
			int num = cur.getAmount()/20;
			int remainder = cur.getAmount() % 20;
			System.out.println("Dispensing "+num+" 20$ note");
			if(remainder !=0) this.chain.dispense(new Currency(remainder));
		}else{
			this.chain.dispense(cur);
		}
	}

}

Dollar10Dispenser.javaを日本語で表現すると、Option 1: 十ドルディスペンサ.java

package com.scdev.design.chainofresponsibility;

public class Dollar10Dispenser implements DispenseChain {

	private DispenseChain chain;
	
	@Override
	public void setNextChain(DispenseChain nextChain) {
		this.chain=nextChain;
	}

	@Override
	public void dispense(Currency cur) {
		if(cur.getAmount() >= 10){
			int num = cur.getAmount()/10;
			int remainder = cur.getAmount() % 10;
			System.out.println("Dispensing "+num+" 10$ note");
			if(remainder !=0) this.chain.dispense(new Currency(remainder));
		}else{
			this.chain.dispense(cur);
		}
	}

}

ここで注目すべき重要な点は、dispenseメソッドの実装です。すべての実装がリクエストを処理し、金額に基づいて一部またはすべてを処理しようとしていることに気付くでしょう。もし一つのチェーンが完全に処理できない場合、その残りのリクエストを処理するために次のプロセッサーにリクエストを送ります。もしプロセッサーが何も処理できない場合、同じリクエストを次のチェーンに転送します。

責任連鎖デザインパターン – チェーンの作成

これは非常に重要なステップであり、私たちは注意深くチェーンを作成する必要があります。さもないと、プロセッサは一切のリクエストを受け取れないかもしれません。例えば、私たちの実装では、最初のプロセッサチェーンをDollar10Dispenser、次にDollar20Dispenserとして保持すると、リクエストは二番目のプロセッサに転送されず、チェーンは無意味になります。こちらが私たちのATMディスペンサーの実装、ATMDispenseChain.javaです。

package com.scdev.design.chainofresponsibility;

import java.util.Scanner;

public class ATMDispenseChain {

	private DispenseChain c1;

	public ATMDispenseChain() {
		// initialize the chain
		this.c1 = new Dollar50Dispenser();
		DispenseChain c2 = new Dollar20Dispenser();
		DispenseChain c3 = new Dollar10Dispenser();

		// set the chain of responsibility
		c1.setNextChain(c2);
		c2.setNextChain(c3);
	}

	public static void main(String[] args) {
		ATMDispenseChain atmDispenser = new ATMDispenseChain();
		while (true) {
			int amount = 0;
			System.out.println("Enter amount to dispense");
			Scanner input = new Scanner(System.in);
			amount = input.nextInt();
			if (amount % 10 != 0) {
				System.out.println("Amount should be in multiple of 10s.");
				return;
			}
			// process the request
			atmDispenser.c1.dispense(new Currency(amount));
		}

	}

}

上記のアプリケーションを実行すると、以下のような出力が得られます。

Enter amount to dispense
530
Dispensing 10 50$ note
Dispensing 1 20$ note
Dispensing 1 10$ note
Enter amount to dispense
100
Dispensing 2 50$ note
Enter amount to dispense
120
Dispensing 2 50$ note
Dispensing 1 20$ note
Enter amount to dispense
15
Amount should be in multiple of 10s.

Chain of Responsibilitiesデザインパターンのクラス図

私たちのATMのチェーンオブレスポンシビリティデザインパターンの実装例は、以下の画像のようになっています。

Chain of Responsibility デザインパターンの重要なポイントは次の通りです。

  • Client doesn’t know which part of the chain will be processing the request and it will send the request to the first object in the chain. For example, in our program ATMDispenseChain is unaware of who is processing the request to dispense the entered amount.
  • Each object in the chain will have it’s own implementation to process the request, either full or partial or to send it to the next object in the chain.
  • Every object in the chain should have reference to the next object in chain to forward the request to, its achieved by java composition.
  • Creating the chain carefully is very important otherwise there might be a case that the request will never be forwarded to a particular processor or there are no objects in the chain who are able to handle the request. In my implementation, I have added the check for the user entered amount to make sure it gets processed fully by all the processors but we might not check it and throw exception if the request reaches the last object and there are no further objects in the chain to forward the request to. This is a design decision.
  • Chain of Responsibility design pattern is good to achieve lose coupling but it comes with the trade-off of having a lot of implementation classes and maintenance problems if most of the code is common in all the implementations.

JDK内のChain of Responsibilityパターンの例

  • java.util.logging.Logger#log()
  • javax.servlet.Filter#doFilter()

チェーンオブレスポンシビリティデザインパターンについては以上です。このデザインパターンがお気に入りいただけ、理解が深まることを願っています。

コメントを残す 0

Your email address will not be published. Required fields are marked *