2015年6月16日火曜日

1-15. Interpreter パターン

与えられた言語に対して、その文法表現とインタープリタを定義します。

サンプルとして数式を解釈する例を示します。
文法表現を以下のように定義します。
expression ::= number | operation | '(' expression ')'
operation ::= expression operator expression
operator ::= '+' | '-' | '*' | '/' | '%' 
number ::= { '0' | ... | '9' } +
実行結果:
expression=1+(2-3)*4/5%6
exprList=[1.0, 2.0, 3.0, -, 4.0, *, +, 5.0, /, 6.0, %]
-0.6
package design_pattern;

import java.util.Deque;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Interpreter パターン
 * GoF
 * 
 * 与えられた言語に対して、その文法表現とインタープリタを定義します。
 * 
 * サンプルとして数式を解釈する例を示します。
 * 文法表現を以下のように定義します。
 * <pre>
 * expression ::= number | operation | '(' expression ')'
 * operation ::= expression operator expression
 * operator ::= '+' | '-' | '*' | '/' | '%' 
 * number ::= { '0' | ... | '9' } +
 * </pre>
 * 実行結果:
 * <pre>
 * expression=1+(2-3)*4/5%6
 * exprList=[1.0, 2.0, 3.0, -, 4.0, *, +, 5.0, /, 6.0, %]
 * -0.6
 * </pre>
 * 
 * @author 2015/05/29 matsushima
 */
public class InterpreterSample {
	/**
	 * main
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		Parser parser = new InterpreterSample().new Parser();
		parser.parse(args.length >= 1 ? args[0] : "1+(2-3)*4/5%6");
		double value = parser.interpret();
		System.out.println(value);
	}
	/** 数式のパーサ。 */
	public class Parser {
		private Pattern pattern = Pattern.compile("(\\d+)|([+\\-*/%(])|(\\))");
		private LinkedList<Expr> exprList; // パースした数式の BNF リスト
		/** 数式のパース。 */
		public void parse(String expression) {
			System.out.println("expression=" + expression);
			exprList = new LinkedList<>();
			Deque<OperatorExpr> operStack = new LinkedList<>(); // 演算子スタック
			Matcher matcher = pattern.matcher(expression);
			while (matcher.find()) {
				String token = matcher.group();
				if (null != matcher.group(1)) { // 123
					// BNF リストに追加
					this.exprList.add(new NumberExpr(token));
				} else if (null != matcher.group(2)) { // +-*/%(
					OperatorExpr expr = new OperatorExpr(token);
					if (!"(".equals(token)
							&& !operStack.isEmpty()
							&& expr.priority <= operStack.peek().priority) {
						// 優先順位の高い演算子を取り出し
						do {
							this.exprList.add(operStack.pop());
						} while (!operStack.isEmpty()
								&& expr.priority >= operStack.peek().priority);
					}
					// BNF リストに追加
					operStack.push(expr);
				} else if (null != matcher.group(3)) { // )
					// "(" まで演算子を取り出し
					while (!((OperatorExpr)operStack.peek()).operator.equals("(")) {
						this.exprList.add(operStack.pop());
					}
					operStack.pop();
				}
			}
			this.exprList.addAll(operStack);
			System.out.println("exprList=" + this.exprList);
		}
		/** パースした数式の計算。 */
		public double interpret() {
			Deque<Double> valueStack = new LinkedList<>();
			for (Expr expr: this.exprList) {
				expr.interpret(valueStack);
			}
			return valueStack.pop();
		}
	}
	/** expression のインターフェース。 */
	public abstract class Expr {
		public abstract void interpret(Deque<Double> valueStack);
	}
	/** number。 */
	public class NumberExpr extends Expr {
		public double value;
		public NumberExpr(String value) {
			this.value = Double.valueOf(value);
		}
		@Override
		public void interpret(Deque<Double> valueStack) {
			valueStack.push(this.value);
		}
		@Override
		public String toString() {
			return String.valueOf(this.value);
		}
	}
	/** operator。 */
	public class OperatorExpr extends Expr {
		public String operator;
		public int priority;
		public OperatorExpr(String str) {
			this.operator = str;
			switch (this.operator) {
			case "+": this.priority = 1; break;
			case "-": this.priority = 1; break;
			case "*": this.priority = 2; break;
			case "/": this.priority = 2; break;
			case "%": this.priority = 2; break;
			case "(": this.priority = 0; break;
			default: throw new RuntimeException(this.operator);
			}
		}
		@Override
		public void interpret(Deque<Double> valueStack) {
			double value1 = valueStack.pop();
			double value2 = valueStack.pop();
			switch (this.operator) {
			case "+": valueStack.push(value2 + value1); break;
			case "-": valueStack.push(value2 - value1); break;
			case "*": valueStack.push(value2 * value1); break;
			case "/": valueStack.push(value2 / value1); break;
			case "%": valueStack.push(value2 % value1); break;
			default: throw new RuntimeException(this.operator);
			}
		}
		@Override
		public String toString() {
			return this.operator;
		}
	}
}

2015年6月15日月曜日

1-14. Command パターン

リクエストをオブジェクトにカプセル化することで、リクエストの情報はパラメータされ、
また取り消し操作をサポートします。



client はリクエストに応じた ConcreteCommand オブジェクトを作成します。
リクエストは client -> Invoker -> ConcreteCommand -> Receiver の順に処理されます。
また、Invoker では Command の保存処理が行われます。
取り消し操作は Invoker に保存された Command を取り出し、同様に処理されます。
void client() {
	Invoker invoker = new Invoker();
	invoker.execute(new ConcreteCommandA());
	invoker.execute(new ConcreteCommandB());
	invoker.undo();
}
interface Command {
	public void execute(Receiver receiver);
	public void undo(Receiver receiver);
}
class ConcreteCommandA implements Command {
	public void execute(Receiver receiver) { receiver.actionA(); }
	public void undo(Receiver receiver) { receiver.undoA(); }
}
class ConcreteCommandB implements Command {
	public void execute(Receiver receiver) { receiver.actionB(); }
	public void undo(Receiver receiver) { receiver.undoB(); }
}
class Receiver {
	public void actionA() { System.out.println("actionA"); }
	public void actionB() { System.out.println("actionB"); }
	public void undoA() { System.out.println("undoA"); }
	public void undoB() { System.out.println("undoB"); }
}
class Invoker {
	private LinkedList<Command> commands = new LinkedList<>();
	private Receiver receiver = new Receiver();
	public void execute(Command command) {
		this.commands.push(command);
		command.execute(this.receiver);
	}
	public void undo() {
		this.commands.pop().undo(this.receiver);
	}
}
実行結果:
actionA
actionB
undoB

サンプルプログラムとして、簡易的なエディタを示します。
テキスト入力、カーソル移動といった操作を Command 化しています。
実行結果:
// 直接実行
EditorReceiver.input(): hello 
EditorReceiver.input(): japan
EditorReceiver.backspace(): 5
EditorReceiver.input(): word
EditorReceiver.cursor(): -1
EditorReceiver.input(): l
hello world
// Command パターン
EditorReceiver.input(): hello 
EditorReceiver.input(): japan
EditorInvoker.undo(): input[japan]
EditorReceiver.backspace(): 5
EditorInvoker.execute(): remove undo -> input[japan]
EditorReceiver.input(): word
EditorReceiver.cursor(): -1
EditorReceiver.input(): l
hello world
package design_pattern;

import java.util.LinkedList;
import java.util.ListIterator;

/**
 * Command
 * GoF
 * リクエストをオブジェクトにカプセル化することで、リクエストの情報はパラメータされ、
 * また取り消し操作をサポートします。
 * 
 * @author 2015/05/28 matsushima
 */
public class CommandSample {
	/**
	 * main
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		new CommandSample().client();
	}
	/**
	 * Client
	 */
	void client() {
		System.out.println("// 直接実行");
		EditorReceiver receiver = new EditorReceiver();
		receiver.input("hello ");
		receiver.input("japan");
		receiver.backspace(5);
		receiver.input("word");
		receiver.cursor(-1);
		receiver.input("l");
		System.out.println(receiver.result());
		System.out.println("// Command パターン");
		EditorInvoker invoker = new EditorInvoker();
		invoker.execute(new InputConcreteCommand("hello "));
		invoker.execute(new InputConcreteCommand("japan"));
		invoker.undo();
		invoker.execute(new InputConcreteCommand("word"));
		invoker.execute(new CursorConcreteCommand(-1));
		invoker.execute(new InputConcreteCommand("l"));
		System.out.println(invoker.result());
	}

	/** エディタのコマンドのインターフェース。 */
	public interface EditorCommand {
		public void execute(EditorReceiver receiver);
		public void undo(EditorReceiver receiver);
	}
	/** テキスト入力コマンド。 */
	public class InputConcreteCommand implements EditorCommand {
		private String text;
		public InputConcreteCommand(String text) {
			this.text = text;
		}
		public void execute(EditorReceiver receiver) {
			receiver.input(this.text);
		}
		public void undo(EditorReceiver receiver) {
			receiver.backspace(this.text.length());
		}
		public String toString() {
			return "input[" + this.text + "]";
		}
	}
	/** カーソル入力コマンド。 */
	public class CursorConcreteCommand implements EditorCommand {
		private int distance;
		public CursorConcreteCommand(int distance) {
			this.distance = distance;
		}
		public void execute(EditorReceiver receiver) {
			receiver.cursor(this.distance);
		}
		public void undo(EditorReceiver receiver) {
			receiver.cursor(-this.distance);
		}
		public String toString() {
			return "cursor[" + this.distance + "]";
		}
	}
	/** エディタレシーバ。 */
	public class EditorReceiver {
		private StringBuilder text = new StringBuilder();
		private int cursorX = 0;
		public void input(String text) {
			System.out.println("EditorReceiver.input(): " + text);
			this.text.insert(this.cursorX, text);
			this.cursorX += text.length();
		}
		public void backspace(int distance) {
			System.out.println("EditorReceiver.backspace(): " + distance);
			this.text.delete(this.cursorX - distance, this.cursorX);
			this.cursorX -= distance;
		}
		public void cursor(int distance) {
			System.out.println("EditorReceiver.cursor(): " + distance);
			this.cursorX += distance;
		}
		public String result() {
			return this.text.toString();
		}
	}
	/** エディタインボーカ。 */
	public class EditorInvoker {
		private LinkedList<EditorCommand> commands = new LinkedList<>();
		private ListIterator<EditorCommand> commandIt = commands.listIterator();
		EditorReceiver receiver = new EditorReceiver();
		public void execute(EditorCommand command) {
			// undo 済みを削除
			while (this.commandIt.hasNext()) {
				EditorCommand c = this.commandIt.next();
				System.out.println("EditorInvoker.execute(): remove undo -> " + c);
				this.commandIt.remove();
			}
			// command 登録
			this.commandIt.add(command);
			// command 実行
			command.execute(receiver);
		}
		public void undo() {
			// command undo
			EditorCommand command = this.commandIt.previous();
			System.out.println("EditorInvoker.undo(): " + command);
			command.undo(this.receiver);
		}
		public String result() {
			return this.receiver.result();
		}
	}
}

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;
		}
	}
}

2015年6月13日土曜日

1-12. Proxy パターン

ほかのオブジェクトへのアクセスを制御するための代わりとなるものを提供します。



Proxy は RealSubject と同じ Subject インターフェースを実装し、
主に RealSubject の呼び出しを制御して負荷を軽減する処理を行います。
void client() {
	Subject subject = new Proxy();
	subject.request();
}
interface Subject {
	public void request();
}
class RealSubject implements Subject {
	public RealSubject() {
		System.out.println("RealSubject.RealSubject();");
	}
	public void request() {
		System.out.println("RealSubject.request();");
	}
}
class Proxy implements Subject {
	private Subject subjectImpl = null;
	public void request() {
		System.out.println("Proxy.request();");
		if (null == this.subjectImpl) {
			this.subjectImpl = new RealSubject();
		}
		this.subjectImpl.request();
	}
}
実行結果:
Proxy.request();
RealSubject.RealSubject();
RealSubject.request();
サンプルプログラムは、画像描画時までロードを遅延させる Proxy の例です。

実行結果:
java.png: RealImageManager_RealSubject(): 画像ロード
duke.png: ProxyImageManager_Proxy(): 画像はロードしない
MyFrame.paint()
java.png: RealImageManager_RealSubject.drawImage(): 画像描画
duke.png: ProxyImageManager_Proxy.drawImage(): 画像描画
duke.png: RealImageManager_RealSubject(): 画像ロード
duke.png: RealImageManager_RealSubject.drawImage(): 画像描画
imageUpdate: イメージ読み込み完了通知 -> paint()
MyFrame.paint()
java.png: RealImageManager_RealSubject.drawImage(): 画像描画
duke.png: ProxyImageManager_Proxy.drawImage(): 画像描画
duke.png: ProxyImageManager_Proxy.drawImage(): 画像ロード済み
duke.png: RealImageManager_RealSubject.drawImage(): 画像描画
imageUpdate: イメージ読み込み完了通知 -> paint()
MyFrame.paint()
java.png: RealImageManager_RealSubject.drawImage(): 画像描画
duke.png: ProxyImageManager_Proxy.drawImage(): 画像描画
duke.png: ProxyImageManager_Proxy.drawImage(): 画像ロード済み
duke.png: RealImageManager_RealSubject.drawImage(): 画像描画

package design_pattern;

import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.ImageObserver;

/**
 * Proxy
 * GoF
 * ほかのオブジェクトへのアクセスを制御するための代わりとなるものを提供します。
 * 
 * @author 2015/05/26 matsushima
 */
public class ProxySample {
	/**
	 * main
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		new ProxySample().new MyFrame();
	}
	/** 画像表示用ウィンドウ。 */
	class MyFrame extends Frame {
		private static final long serialVersionUID = 1L;
		private ImageManager_Subject imageSubject1, imageSubject2;
		public MyFrame() {
			super("ProxySample");
			this.imageSubject1 = new RealImageManager_RealSubject("java.png");
			this.imageSubject2 = new ProxyImageManager_Proxy("duke.png");
			this.addWindowListener(new WindowAdapter() {
				public void windowClosing(WindowEvent e) { System.exit(0); }
			});
			this.setSize(300, 250);
			this.setVisible(true);
		}
		@Override
		public void paint(Graphics g) {
			System.out.println("MyFrame.paint()");
			super.paint(g);
			this.imageSubject1.drawImage(g, 50, 50, this);
			this.imageSubject2.drawImage(g, 150, 50, this);
		}
		@Override
		public boolean imageUpdate(Image img, int infoflags, int x, int y, int w, int h) {
			if (ALLBITS == infoflags) {
				System.out.println("imageUpdate: イメージ読み込み完了通知 -> paint()");
				return super.imageUpdate(img, infoflags, x, y, w, h); // paint() が呼ばれる
			}
			return true;
		}
	}
	/** 画像マネージャのインターフェース。 */
	interface ImageManager_Subject {
		public void drawImage(Graphics g, int x, int y, ImageObserver observer);
	}
	/** 画像マネージャ。 */
	class RealImageManager_RealSubject implements ImageManager_Subject {
		private Image image;
		private String path;
		public RealImageManager_RealSubject(String path) {
			System.out.println(path + ": RealImageManager_RealSubject(): 画像ロード");
			this.path = path;
			this.image = Toolkit.getDefaultToolkit().getImage(path);
		}
		public void drawImage(Graphics g, int x, int y, ImageObserver observer) {
			System.out.println(path + ": RealImageManager_RealSubject.drawImage(): 画像描画");
			g.drawImage(this.image, x, y, observer);
		}
	}
	/** 描画時に画像をロードする画像マネージャのプロキシ。 */
	class ProxyImageManager_Proxy implements ImageManager_Subject {
		private ImageManager_Subject subjectImpl = null;
		private String path;
		public ProxyImageManager_Proxy(String path) {
			System.out.println(path + ": ProxyImageManager_Proxy(): 画像はロードしない");
			this.path = path;
		}
		public void drawImage(Graphics g, int x, int y, ImageObserver observer) {
			System.out.println(path + ": ProxyImageManager_Proxy.drawImage(): 画像描画");
			if (null == this.subjectImpl) {
				this.subjectImpl = new RealImageManager_RealSubject(this.path);
			} else {
				System.out.println(path + ": ProxyImageManager_Proxy.drawImage(): 画像ロード済み");
			}
			this.subjectImpl.drawImage(g, x, y, observer);
		}
	}
}

2015年6月12日金曜日

1-11. Flyweight パターン

多数のオブジェクトを効率的にサポートするために共有を使用します。



オブジェクトを多数生成するとメモリを多く消費してしまいますが、
同じオブジェクトを共有化し再利用することで、効率化します。
ここでオブジェクトは、ほかの利用個所で影響しないように、生成されたあとに
状態が変化しない不変なオブジェクトである必要があります。
class FlyweightFactory {
	private HashMap<String, Flyweight> flyweights = new HashMap<>();
	public Flyweight getFlyWeight(String text) {
		Flyweight flyweight = this.flyweights.get(text);
		if (null == flyweight) {
			flyweight = new Flyweight(text);
			this.flyweights.put(text, flyweight);
		}
		return flyweight;
	}
}
class Flyweight {
	private String text;
	public Flyweight(String text) { this.text = text; }
	public String getText() { return this.text; }
}
void client() {
	FlyweightFactory factory = new FlyweightFactory();
	Flyweight f1 = factory.getFlyWeight("abc");
	Flyweight f2 = factory.getFlyWeight("123");
	Flyweight f3 = factory.getFlyWeight("abc");
	System.out.println(f1 + ": " + f1.getText());
	System.out.println(f2 + ": " + f2.getText());
	System.out.println(f3 + ": " + f3.getText());
}
出力結果:
design_pattern.FlyweightSample$Flyweight@6d14382d: abc
design_pattern.FlyweightSample$Flyweight@4a744a4d: 123
design_pattern.FlyweightSample$Flyweight@6d14382d: abc
サンプルプログラムとして、DOM ノードを構築する例を示します。
このうち、Text_Flyweight クラスの生成を、Flyweight パターンで実装しています。

出力結果:
<root>
  <entry><name>Google(147567651)</name><URL>http://www.google.co.jp/(1973381719)</URL></entry>
  <entry><name>Yahoo(889106965)</name><URL>http://www.yahoo.co.jp/(1029544929)</URL></entry>
  <entry><name>bing(1285877824)</name><URL>http://www.bing.com/(1620648207)</URL></entry>
</root>
package design_pattern;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

/**
 * Flyweight パターン
 * GoF
 * 多数のオブジェクトを効率的にサポートするために共有を使用します。
 * 
 * @author 2015/05/26 matsushima
 */
public class FlyweightSample {

	public static void main(String[] args) {
		new FlyweightSample().client();
	}

	void client() {
		Document_FlyweightFactory document = new Document_FlyweightFactory("root");
		Element element;
		document.appendChild(element = document.createElement("entry"));
		element.appendChild(document.createElement("name"))
				.appendChild(document.createTextNode("Google"));
		element.appendChild(document.createElement("URL"))
				.appendChild(document.createTextNode("http://www.google.co.jp/"));
		document.appendChild(element = document.createElement("entry"));
		element.appendChild(document.createElement("name"))
				.appendChild(document.createTextNode("Yahoo"));
		element.appendChild(document.createElement("URL"))
				.appendChild(document.createTextNode("http://www.yahoo.co.jp/"));
		document.appendChild(element = document.createElement("entry"));
		element.appendChild(document.createElement("name"))
				.appendChild(document.createTextNode("bing"));
		element.appendChild(document.createElement("URL"))
		.appendChild(document.createTextNode("http://www.bing.com/"));
		printNodeImpl(document, "");
	}
	void printNodeImpl(Node node, String indent) {
		if (node instanceof Text_Flyweight) {
			System.out.print(node.getNodeValue() + "(" + node.hashCode() + ")");
		} else {
			String r2 = (2 == indent.length() ? "\n" + indent : "");
			String r0 = (0 == indent.length() ? "\n" : "");
			System.out.print(r2 + "<" + node.getNodeName() + ">");
			for (Node child: node.getChildNodes()) {
				this.printNodeImpl(child, indent + "  ");
			}
			System.out.print(r0 + "</" + node.getNodeName() + ">" + r0);
		}
	}
	
	/** Node インターフェース。 */
	public interface Node {
		public String getNodeName();
		public String getNodeValue();
		public Node appendChild(Node node);
		public List<Node> getChildNodes();
	}
	/** Text Flyweight クラス。 */
	public class Text_Flyweight implements Node {
		private String value;
		protected Text_Flyweight(String value) { this.value = value; }
		public String getNodeName() { return "#text"; }
		public String getNodeValue() { return this.value; }
		public Node appendChild(Node node) { return null; }
		public List<Node> getChildNodes() { return null; }
	}
	/** Element クラス。 */
	public class Element implements Node {
		private String name;
		private LinkedList<Node> children = new LinkedList<>();
		protected Element(String name) { this.name = name; }
		public String getNodeName() { return this.name; }
		public String getNodeValue() { return null; }
		public Node appendChild(Node node) {
			this.children.add(node);
			return node;
		}
		public List<Node> getChildNodes() {
			return this.children;
		}
	}
	/** Document Flyweight Factory クラス。 */
	public class Document_FlyweightFactory implements Node {
		private HashMap<String, Text_Flyweight> textFlyweights = new HashMap<>();
		private String name;
		private LinkedList<Node> children = new LinkedList<>();
		protected Document_FlyweightFactory(String name) { this.name = name; }
		public String getNodeName() { return this.name; }
		public String getNodeValue() { return null; }
		public Node appendChild(Node node) {
			this.children.add(node);
			return node;
		}
		public List<Node> getChildNodes() {
			return this.children;
		}
		public Element createElement(String name) {
			return new Element(name);
		}
		public Text_Flyweight createTextNode(String value) {
			Text_Flyweight node = this.textFlyweights.get(value);
			if (null == node) {
				node = new Text_Flyweight(value);
				this.textFlyweights.put(value, node);
			}
			return node;
		}
	}
}

1-10. Facade パターン

サブシステム中のインターフェースのセットへの統一されたインターフェースを提供します。



1つのサブシステムが複数のクラスからなる場合、Client からは直接クラスにアクセスせずに、
Facade クラスのインターフェースを介して機能を利用します。
システムが複数のサブシステムからなる場合、サブシステム間の依存度を減らすことができます。
void client() {
	new Facade().operation();
}
class Facade {
	ComponentA componentA = new ComponentA();
	ComponentB componentB = new ComponentB();
	ComponentC componentC = new ComponentC();
	public void operation() {
		this.componentA.operationA();
		this.componentB.operationB();
		this.componentC.operationC();
	}
}
class ComponentA {
	public void operationA() {
		System.out.println("A");
	}
}
class ComponentB {
	public void operationB() {
		System.out.println("B");
	}
}
class ComponentC {
	public void operationC() {
		System.out.println("C");
	}
}
出力結果:
A
B
C

サンプルプログラムとして、CSV から XML へ変換するコンバータを示します。
Facade パターンでは、Facade クラスのインターフェースを介してサブシステムにアクセスし、
サブシステム内には、パーサ、ジェネレータ、ノードなどのクラスがありますが、
直接アクセスすることはありません。

出力結果:
// CSV -> XML コンバート
<root>
  <entry><name>Google</name><URL>http://www.google.co.jp/</URL></entry>
  <entry><name>Yahoo</name><URL>http://www.yahoo.co.jp/</URL></entry>
  <entry><name>bing</name><URL>http://www.bing.com/</URL></entry>
</root>
// CSV -> XML コンバータ。(Facade パターン)
<root>
  <entry><name>Google</name><URL>http://www.google.co.jp/</URL></entry>
  <entry><name>Yahoo</name><URL>http://www.yahoo.co.jp/</URL></entry>
  <entry><name>bing</name><URL>http://www.bing.com/</URL></entry>
</root>
package design_pattern;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.util.LinkedList;
import java.util.List;

/**
 * Facade パターン
 * GoF, Code Complete
 * サブシステム中のインターフェースのセットへの統一されたインターフェースを提供します。
 * 
 * @author 2015/05/26 matsushima
 */
public class FacadeSample {
	/**
	 * main
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			new FacadeSample().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"
				;
		System.out.println("// CSV -> XML コンバート");
		// CSV パーサ。
		CsvParser_Component parser = new CsvParser_Component();
		Document_Component document = parser.parse(new StringReader(csvText));
		// XML で出力。
		XmlGenerator_Component generator = new XmlGenerator_Component();
		generator.print(new OutputStreamWriter(System.out), document);
		System.out.println("// CSV -> XML コンバータ。(Facade パターン)");
		new CsvToXmlConverter_Facade().convert(
				new StringReader(csvText),
				new OutputStreamWriter(System.out));
	}

	/** CSV -> XML コンバータ。 */
	public class CsvToXmlConverter_Facade {
		public void convert(Reader in, Writer out) throws IOException {
			// CSV パーサ。
			CsvParser_Component parser = new CsvParser_Component();
			Document_Component document = parser.parse(in);
			// XML で出力。
			XmlGenerator_Component generator = new XmlGenerator_Component();
			generator.print(out, document);
		}
	}
	/** CSV パーサ。 */
	public class CsvParser_Component {
		public Document_Component parse(Reader in) throws IOException {
			Document_Component document = new Document_Component("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_Component entry = document.appendChild(new Element_Component("entry"));
				for (int i = 0; i < Math.min(names.length, cols.length); ++ i) {
					entry.appendChild(new Element_Component(names[i]))
							.appendChild(new Text_Component(cols[i]));
				}
			}
			return document;
		}
	}
	/** XML で出力。 */
	public class XmlGenerator_Component {
		public void print(Writer writer, Document_Component document) throws IOException {
			this.printNodeImpl(new PrintWriter(writer), document, "");
			writer.flush();
		}
		private void printNodeImpl(PrintWriter writer, Node_Component node, String indent) {
			if (node instanceof Text_Component) {
				writer.print(node.getNodeValue());
			} else {
				String r2 = (2 == indent.length() ? "\n" + indent : "");
				String r0 = (0 == indent.length() ? "\n" : "");
				writer.print(r2 + "<" + node.getNodeName() + ">");
				for (Node_Component child: node.getChildNodes()) {
					this.printNodeImpl(writer, child, indent + "  ");
				}
				writer.print(r0 + "</" + node.getNodeName() + ">" + r0);
			}
		}
	}
	/** Node Component インターフェース。 */
	public interface Node_Component {
		public String getNodeName();
		public String getNodeValue();
		public Node_Component appendChild(Node_Component node);
		public List<Node_Component> getChildNodes();
	}
	/** Text Component クラス。 */
	public class Text_Component implements Node_Component {
		private String value;
		protected Text_Component(String value) { this.value = value; }
		public String getNodeName() { return "#text"; }
		public String getNodeValue() { return this.value; }
		public Node_Component appendChild(Node_Component node) { return null; }
		public List<Node_Component> getChildNodes() { return null; }
	}
	/** Element Component クラス。 */
	public class Element_Component implements Node_Component {
		private String name;
		private LinkedList<Node_Component> children = new LinkedList<>();
		protected Element_Component(String name) { this.name = name; }
		public String getNodeName() { return this.name; }
		public String getNodeValue() { return null; }
		public Node_Component appendChild(Node_Component node) {
			this.children.add(node);
			return node;
		}
		public List<Node_Component> getChildNodes() {
			return this.children;
		}
	}
	/** Document Component クラス。 */
	public class Document_Component implements Node_Component {
		private String name;
		private LinkedList<Node_Component> children = new LinkedList<>();
		protected Document_Component(String name) { this.name = name; }
		public String getNodeName() { return this.name; }
		public String getNodeValue() { return null; }
		public Node_Component appendChild(Node_Component node) {
			this.children.add(node);
			return node;
		}
		public List<Node_Component> getChildNodes() {
			return this.children;
		}
		public Element_Component createElement(String name) { return new Element_Component(name); }
		public Text_Component createTextNode(String value) { return new Text_Component(value); }
	}
}