2015年6月9日火曜日

1-7. Bridge パターン

2つのクラスが依存しないように、実装から機能を分離します。



以下のように ImplementorA を継承して機能 A1, A2 を実装したとします。
    public interface ImplementorA {
        public void implementationA();
    }
    public class ConcreteImplementorA1 implements ImplementorA {
        public void implementationA() {
           // 機能 A1 の実装
        }
    }
    public class ConcreteImplementorA2 implements ImplementorA {
        public void implementationA() {
            機能 A2 の実装
        }
    }
そこからさらに、機能 B1, B2 を実装するとします。
    public class RefinedAbstractionB1A1 extends ConcreteImplementorA1 {
        public void refinedMethodB1() {
            this.implementationA(); // 機能 A1 を使用
            // 機能 B1 の実装
        }
    }
    public class RefinedAbstractionB1A2 extends ConcreteImplementorA2 {
        public void refinedMethodB1() {
            this.implementationA(); // 機能 A2 を使用
            // 機能 B1 の実装
        }
    }
    public class RefinedAbstractionB2A1 extends ConcreteImplementorA1 {
        public void refinedMethodB1() {
            this.implementationA(); // 機能 A1 を使用
            // 機能 B2 の実装
        }
    }
    public class RefinedAbstractionB2A2 extends ConcreteImplementorA2 {
        public void refinedMethodB1() {
            this.implementationA(); // 機能 A2 を使用
            // 機能 B2 の実装
        }
    }
上記の実装方法だと、
・機能 A3, A4, ... と増やしたときに、機能 Bx のコピーが増える。
・機能 B3, B4, ... と増やしたときに、機能 Ax ごとに実装する必要がある。
・クラスが AxBx と増えてしまう。
といった問題が発生します。
Bridge パターンでは、以下のように継承でなく委譲することで実装をクラス階層から分離します。
    public abstract class AbstractionB {
        private ImplementorA implA;
        public void methodB() {
            this.implA.implementationA(); // 機能 Ax の使用
            // 機能 B の実装
        }
    }
    public class RefinedAbstractionB1 extends AbstractionB {
        public void refinedMethodB1() {
            this.methodB(); // 機能 B の使用
            // 機能 B1 の実装
        }
    }
    public class RefinedAbstractionB2 extends AbstractionB {
        public void refinedMethodB2() {
            this.methodB(); // 機能 B の使用
            // 機能 B2 の実装
        }
    }
サンプルプログラムは、CSV, XML から DOM を構築するパーサに、
XML, Json 形式で出力する機能を追加しています。

出力結果:
    // 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>
    // XML -> Json
    {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.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;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Bridge パターン
 * GoF, Code Complete
 * 2つのクラスが依存しないように、実装から機能を分離します。
 * 
 * 以下のように ImplementorA を継承して機能 A1, A2 を実装したとします。
 * <pre>
 *     public interface ImplementorA {
 *         public void implementationA();
 *     }
 *     public class ConcreteImplementorA1 implements ImplementorA {
 *         public void implementationA() {
 *            // 機能 A1 の実装
 *         }
 *     }
 *     public class ConcreteImplementorA2 implements ImplementorA {
 *         public void implementationA() {
 *             機能 A2 の実装
 *         }
 *     }
 * </pre>
 * そこからさらに、機能 B1, B2 を実装するとします。
 * <pre>
 *     public class RefinedAbstractionB1A1 extends ConcreteImplementorA1 {
 *         public void refinedMethodB1() {
 *             this.implementationA(); // 機能 A1 を使用
 *             // 機能 B1 の実装
 *         }
 *     }
 *     public class RefinedAbstractionB1A2 extends ConcreteImplementorA2 {
 *         public void refinedMethodB1() {
 *             this.implementationA(); // 機能 A2 を使用
 *             // 機能 B1 の実装
 *         }
 *     }
 *     public class RefinedAbstractionB2A1 extends ConcreteImplementorA1 {
 *         public void refinedMethodB1() {
 *             this.implementationA(); // 機能 A1 を使用
 *             // 機能 B2 の実装
 *         }
 *     }
 *     public class RefinedAbstractionB2A2 extends ConcreteImplementorA2 {
 *         public void refinedMethodB1() {
 *             this.implementationA(); // 機能 A2 を使用
 *             // 機能 B2 の実装
 *         }
 *     }
 * </pre>
 * 上記の実装方法だと、
 * ・機能 A3, A4, ... と増やしたときに、機能 Bx のコピーが増える。
 * ・機能 B3, B4, ... と増やしたときに、機能 Ax ごとに実装する必要がある。
 * ・クラスが Ax * Bx と増えてしまう。
 * といった問題が発生します。
 * Bridge パターンでは、以下のように継承でなく委譲することで実装をクラス階層から分離します。
 * <pre>
 *     public abstract class AbstractionB {
 *         private ImplementorA implA;
 *         public void methodB() {
 *             this.implA.implementationA(); // 機能 Ax の使用
 *             // 機能 B の実装
 *         }
 *     }
 *     public class RefinedAbstractionB1 extends AbstractionB {
 *         public void refinedMethodB1() {
 *             this.methodB(); // 機能 B の使用
 *             // 機能 B1 の実装
 *         }
 *     }
 *     public class RefinedAbstractionB2 extends AbstractionB {
 *         public void refinedMethodB2() {
 *             this.methodB(); // 機能 B の使用
 *             // 機能 B2 の実装
 *         }
 *     }
 * </pre>
 * サンプルプログラムは、CSV, XML から DOM を構築するパーサに、
 * XML, Json 形式で出力する機能を追加しています。
 * 
 * 出力結果:
 * <pre>
 *     // 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>
 *     // XML -> Json
 *     {root:
 *       {entry:
 *         {name:
 *           apple
 *         }
 *        ,{price:
 *           100
 *         }
 *       }
 *      ,{entry:
 *         {name:
 *           orange
 *         }
 *        ,{price:
 *           50
 *         }
 *       }
 *      ,{entry:
 *         {name:
 *           banana
 *         }
 *        ,{price:
 *           200
 *         }
 *       }
 *     }
 * </pre>
 * 
 * @author 2015/05/25 matsushima
 */
public class BridgeSample {
	/**
	 * main
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			new BridgeSample().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>"
				;
		System.out.println("// CSV -> XML");
		new XmlConverter_RefinedAbstraction(new CsvParser_ConcreteImplementor()).convert(
				new StringReader(csvText), new OutputStreamWriter(System.out));
		System.out.println("// XML -> Json");
		new JsonConverter_RefinedAbstraction(new XmlParser_ConcreteImplementor()).convert(
				new StringReader(xmlText), new OutputStreamWriter(System.out));
	}
	
	/** ノード。 */
	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; }
	}
	
	// Implementor ///////////////////////////////////////
	/** パーサのインターフェース。 */
	public interface Parser_Implementor {
		public Node parse(Reader in) throws IOException;
	}
	/** CSV パーサ。 */
	public class CsvParser_ConcreteImplementor implements Parser_Implementor {
		public Node parse(Reader in) throws IOException {
			Node root = new Node("root");
			String[] names = null;
			BufferedReader reader = new BufferedReader(in);
			for (String line; null != (line = reader.readLine()); ) {
				String[] cols = line.split(",");
				if (null == names) {
					names = cols;
				} else {
					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_ConcreteImplementor implements Parser_Implementor {
		private final Pattern p1 = Pattern.compile("\\s*<\\s*([^>/]*)\\s*>\\s*");
		private final Pattern p2 = Pattern.compile("\\s*</\\s*([^>]*)\\s*>\\s*");
		public Node parse(Reader in) throws IOException {
			// 入力データ読み込み
			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;
		}
	}
	
	// Abstraction ///////////////////////////////////////
	/** コンバータの抽象クラス。 */
	public abstract class Converter_Abstraction {
		private Parser_Implementor impl;
		protected Converter_Abstraction(Parser_Implementor impl) {
			this.impl = impl;
		}
		public Node parse(Reader in) throws IOException {
			return this.impl.parse(in);
		}
	}
	/** XML へ変換。 */
	public class XmlConverter_RefinedAbstraction extends Converter_Abstraction {
		protected XmlConverter_RefinedAbstraction(Parser_Implementor impl) {
			super(impl);
		}
		public void convert(Reader in, Writer out) throws IOException {
			this.printNodeImpl(new PrintWriter(out), this.parse(in), "");
			out.flush();
		}
		private void printNodeImpl(PrintWriter writer, Node node, String indent) {
			if (node.getChildNodes().isEmpty()) {
				writer.println(indent + node.getText());
			} else {
				writer.println(indent + "<" + node.getText() + ">");
				for (Node child: node.getChildNodes()) {
					this.printNodeImpl(writer, child, indent + "  ");
				}
				writer.println(indent + "</" + node.getText() + ">");
			}
		}
	}
	/** Json へ変換。 */
	public class JsonConverter_RefinedAbstraction extends Converter_Abstraction {
		protected JsonConverter_RefinedAbstraction(Parser_Implementor impl) {
			super(impl);
		}
		public void convert(Reader in, Writer out) throws IOException {
			this.printNodeImpl(new PrintWriter(out), this.parse(in), "");
			out.flush();
		}
		private void printNodeImpl(PrintWriter writer, Node node, String indent) {
			if (node.getChildNodes().isEmpty()) {
				writer.println(indent + node.getText());
			} else {
				writer.println(indent + "{" + node.getText() + ":");
				indent = indent.replace(",", " ");
				for (Node child: node.getChildNodes()) {
					this.printNodeImpl(writer, child,
							indent + (child != node.getChildNodes().get(0) ? " ," : "  "));
				}
				writer.println(indent + "}");
			}
		}
	}
}

0 件のコメント:

コメントを投稿