2015年6月14日日曜日

1-13. Chain of Responsibility パターン

1つ以上のオブジェクトにリクエストを扱う機会を与えることによって、
リクエストの送信元と送信先を結合させることを避けます。
受信オブジェクトをチェーンにし、リクエストをオブジェクトが処理するまで
チェーンにそって通過させます。



リクエストの種類によって処理できる Handler が変わる場合、
例えばリクエストaは ConcreteHandlerA、リクエストbは ConcreteHandlerB、...
が処理できる場合を考えます。
Chain of Responsibility パターンではこれらの Handler を数珠つなぎにしておき、
リクエストを自分自身が処理できない場合に次の Handler にリクエストを渡します。
これによって Client はどの Handler かを意識せずに処理結果だけを得ることができます。
void client() {
	Handler handler = new ConcreteHandlerA(null);
	handler = new ConcreteHandlerB(handler);
	handler = new ConcreteHandlerC(handler);
	handler.handleRequest("a");
	handler.handleRequest("b");
	handler.handleRequest("c");
	handler.handleRequest("d");
}
abstract class Handler {
	private Handler next;
	protected Handler(Handler next) { this.next = next; }
	public void handleRequest(String type) {
		if (null != this.next) {
			this.next.handleRequest(type);
		} else {
			System.out.println("Handler: nobody handled " + type);
		}
	}
}
class ConcreteHandlerA extends Handler {
	public ConcreteHandlerA(Handler next) { super(next); }
	public void handleRequest(String type) {
		if ("a".equals(type)) {
			System.out.println("ConcreteHandlerA: handle " + type);
		} else {
			super.handleRequest(type);
		}
	}
}
class ConcreteHandlerB extends Handler {
	public ConcreteHandlerB(Handler next) { super(next); }
	public void handleRequest(String type) {
		if ("b".equals(type)) {
			System.out.println("ConcreteHandlerB: handle " + type);
		} else {
			super.handleRequest(type);
		}
	}
}
class ConcreteHandlerC extends Handler {
	public ConcreteHandlerC(Handler next) { super(next); }
	public void handleRequest(String type) {
		if ("c".equals(type)) {
			System.out.println("ConcreteHandlerC: handle " + type);
		} else {
			super.handleRequest(type);
		}
	}
}
実行結果:
ConcreteHandlerA: handle a
ConcreteHandlerB: handle b
ConcreteHandlerC: handle c
Handler: nobody handled d

サンプルプログラムとして、CSV, XML のパーサをチェーンにしてパースを行う
サンプルを示します。

実行結果:
root: 
  entry: name: Google URL: http://www.google.co.jp/ 
  entry: name: Yahoo URL: http://www.yahoo.co.jp/ 
  entry: name: bing URL: http://www.bing.com/ 
root: 
  entry: name: apple price: 100 
  entry: name: orange price: 50 
  entry: name: banana price: 200 
package design_pattern;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Chain of Responsibility
 * GoF
 * 
 * 1つ以上のオブジェクトにリクエストを扱う機会を与えることによって、
 * リクエストの送信元と送信先を結合させることを避けます。
 * 受信オブジェクトをチェーンにし、リクエストをオブジェクトが処理するまで
 * チェーンにそって通過させます。
 * 
 * @author 2015/05/27 matsushima
 */
public class ChainOfResponsibilitySample {
	/**
	 * main
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			new ChainOfResponsibilitySample().client();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**
	 * Client
	 */
	void client() throws IOException {
		String csvText =
				"name,URL\n"
				+ "Google,http://www.google.co.jp/\n"
				+ "Yahoo,http://www.yahoo.co.jp/\n"
				+ "bing,http://www.bing.com/\n"
				;
		String xmlText =
				"<entry><name>apple</name><price>100</price></entry>"
				+ "<entry><name>orange</name><price>50</price></entry>"
				+ "<entry><name>banana</name><price>200</price></entry>"
				;
		Parser_Handler parser = new CsvParser_ConcreteComponent(null);
		parser = new XmlParser_ConcreteComponent(parser);
		Node csvNode = parser.parse("text/csv", new StringReader(csvText));
		Node xmlNode = parser.parse("text/xml", new StringReader(xmlText));
		printNode(System.out, csvNode, "");
		printNode(System.out, xmlNode, "");
	}
	/**
	 * ノードを出力。
	 */
	void printNode(PrintStream writer, Node node, String indent) {
		if (node.getChildNodes().isEmpty()) {
			writer.print(node.getText() + " ");
		} else {
			String r2 = (2 == indent.length() ? "\n" + indent : indent.trim());
			String r0 = (0 == indent.length() ? "\n" : "");
			writer.print(r2 + node.getText() + ": ");
			for (Node child: node.getChildNodes()) {
				printNode(writer, child, indent + "  ");
			}
			writer.print(r0);
		}
	}

	/** ノード。 */
	public class Node {
		private String text;
		private LinkedList<Node> children = new LinkedList<>();
		public Node(String text) { this.text = text; }
		public String getText() { return this.text; }
		public Node appendChild(Node node) { this.children.add(node); return node; }
		public List<Node> getChildNodes() { return this.children; }
	}
	/** パーサの抽象クラス。 */
	public abstract class Parser_Handler {
		private Parser_Handler next;
		protected Parser_Handler(Parser_Handler next) {
			this.next = next;
		}
		public Node parse(String mime, Reader in) throws IOException {
			if (null != this.next) {
				return this.next.parse(mime, in);
			} else {
				return null;
			}
		}
	}
	/** CSV パーサ。 */
	public class CsvParser_ConcreteComponent extends Parser_Handler {
		public CsvParser_ConcreteComponent(Parser_Handler next) {
			super(next);
		}
		public Node parse(String mime, Reader in) throws IOException {
			if (!"text/csv".equals(mime)) { // 処理できない
				return super.parse(mime, in); // 次に渡す
			}
			Node root = new Node("root");
			BufferedReader reader = new BufferedReader(in);
			String line = reader.readLine();
			String[] names = (null == line ? null : line.split(","));
			while (null != (line = reader.readLine())) {
				String[] cols = line.split(",");
				Node entry = root.appendChild(new Node("entry"));
				for (int i = 0; i < Math.min(names.length, cols.length); ++ i) {
					entry.appendChild(new Node(names[i])).appendChild(new Node(cols[i]));
				}
			}
			return root;
		}
	}
	/** XML パーサ。 */
	public class XmlParser_ConcreteComponent extends Parser_Handler {
		public XmlParser_ConcreteComponent(Parser_Handler next) {
			super(next);
		}
		private final Pattern p1 = Pattern.compile("\\s*<\\s*([^>/]*)\\s*>\\s*");
		private final Pattern p2 = Pattern.compile("\\s*</\\s*([^>]*)\\s*>\\s*");
		public Node parse(String mime, Reader in) throws IOException {
			if (!"text/xml".equals(mime)) { // 処理できない
				return super.parse(mime, in); // 次に渡す
			}
			// 入力データ読み込み
			StringBuilder sb = new StringBuilder();
			char[] buf = new char[1024];
			for (int read; -1 != (read = in.read(buf)); ) {
				sb.append(buf, 0, read);
			}
			// ノードごとのパース処理
			Node root = new Node("root");
			this.parseImpl(root, sb, 0, p1.matcher(sb), p2.matcher(sb));
			return root;
		}
		private int parseImpl(Node node, CharSequence text, int index, Matcher m1, Matcher m2) {
			while (index < text.length()) {
				int i1 = m1.find(index) ? m1.start() : text.length();
				int i2 = m2.find(index) ? m2.start() : text.length();
				if (i1 < i2) { // element
					Node childElement = new Node(m1.group(1));
					node.appendChild(childElement);
					index = this.parseImpl(childElement, text, m1.end(), m1, m2);
				} else { // text
					String value = text.subSequence(index, i2).toString().trim();
					if (!value.isEmpty()) {
						node.appendChild(new Node(value));
					}
					index = i2 < text.length() ? m2.end() : text.length();
					break;
				}
			}
			return index;
		}
	}
}

0 件のコメント:

コメントを投稿