2015年6月8日月曜日

1-6. Adapter パターン

既存のクラスのインターフェースを変換します。



既存のクラスのインターフェースがそのままでは使用できない場合、
インターフェースを変換する Adapter を用意することで、
既存クラスを変更することなく既存クラスの機能を使用可能にします。

既存の以下のようなクラスがある場合、
    public class Adaptee {
        public int oldMethod() {
            return 100;
        }
    }
必要となる新たなインターフェースを定義します。
    public interface Target {
        public int requiredMethod();
    }
既存クラスを継承して新たなインターフェースを実装します。
    public class Adapter extends Adaptee implements Target {
        public int requiredMethod() {
            return super.oldMethod();
        }
    }
あるいは、既存クラスを委譲してして新たなインターフェースを実装します。
    public class Adapter extends Adaptee implements Target {
        private Adaptee adaptee = new Adaptee();
        public int requiredMethod() {
            return this.adaptee.oldMethod();
        }
    }

サンプルプログラムは、CSV から XML, Json 形式のテキストを変換するコンバータの
インターフェースを変更するアダプタの例です。

CsvToXmlConverter_Adaptee:
CSV データの文字列を XML データの文字列に変換する(文字列 -> 文字列)。

CsvToJsonConverter_Adaptee:
CSV データファイルを Json データファイルに変換する(ファイル -> ファイル)。

CsvToXmlConverter_Adapter:
CSV データを XML データに変換する(Reader -> Writer)。
文字列 -> 文字列を Reader -> Writer に変換。
継承による実装。

CsvToJsonConverter_Adapter:
CSV データを Json データに変換する(Reader -> Writer)。
ファイル -> ファイルを Reader -> Writer に変換。
委譲による実装。

出力結果:
    // CSV データの文字列を XML データの文字列に変換する(文字列 -> 文字列)。
    <sites>
     <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>
    </sites>
    // CSV データファイルを Json データファイルに変換する(ファイル -> ファイル)。
    {sites:
      {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/}
      }
    }
    // CSV データを XML データに変換する(Reader -> Writer)。
    文字列 -> 文字列を Reader -> Writer に変換。
    継承による実装。
    <sites>
     <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>
    </sites>
    // CSV データを Json データに変換する(Reader -> Writer)。
    ファイル -> ファイルを Reader -> Writer に変換。
    委譲による実装。
    {sites:
      {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/}
      }
    }
package design_pattern;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;

/**
 * Adapter
 * GoF, Code Complete
 * 既存のクラスのインターフェースを変換します。
 * 
 * 既存のクラスのインターフェースがそのままでは使用できない場合、
 * インターフェースを変換する Adapter を用意することで、
 * 既存クラスを変更することなく既存クラスの機能を使用可能にします。
 * 
 * 既存の以下のようなクラスがある場合、
 * <pre>
 *     public class Adaptee {
 *         public int oldMethod() {
 *             return 100;
 *         }
 *     }
 * </pre>
 * 必要となる新たなインターフェースを定義します。
 * <pre>
 *     public interface Target {
 *         public int requiredMethod();
 *     }
 * </pre>
 * 既存クラスを継承して新たなインターフェースを実装します。
 * <pre>
 *     public class Adapter extends Adaptee implements Target {
 *         public int requiredMethod() {
 *             return super.oldMethod();
 *         }
 *     }
 * </pre>
 * あるいは、既存クラスを委譲してして新たなインターフェースを実装します。
 * <pre>
 *     public class Adapter extends Adaptee implements Target {
 *         private Adaptee adaptee = new Adaptee();
 *         public int requiredMethod() {
 *             return this.adaptee.oldMethod();
 *         }
 *     }
 * </pre>
 * 
 * サンプルプログラムは、CSV から XML, Json 形式のテキストを変換するコンバータの
 * インターフェースを変更するアダプタの例です。
 * 
 * CsvToXmlConverter_Adaptee:
 *     CSV データの文字列を XML データの文字列に変換する(文字列 -> 文字列)。
 * 
 * CsvToJsonConverter_Adaptee:
 *     CSV データファイルを Json データファイルに変換する(ファイル -> ファイル)。
 * 
 * CsvToXmlConverter_Adapter:
 *     CSV データを XML データに変換する(Reader -> Writer)。
 *     文字列 -> 文字列を Reader -> Writer に変換。
 *     継承による実装。
 * 
 * CsvToJsonConverter_Adapter:
 *     CSV データを Json データに変換する(Reader -> Writer)。
 *     ファイル -> ファイルを Reader -> Writer に変換。
 *     委譲による実装。
 * 
 * 出力結果:
 * <pre>
 *     // CSV データの文字列を XML データの文字列に変換する(文字列 -> 文字列)。
 *     <sites>
 *      <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>
 *     </sites>
 *     // CSV データファイルを Json データファイルに変換する(ファイル -> ファイル)。
 *     {sites:
 *       {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/}
 *       }
 *     }
 *     // CSV データを XML データに変換する(Reader -> Writer)。
 *      * 文字列 -> 文字列を Reader -> Writer に変換。
 *      * 継承による実装。
 *     <sites>
 *      <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>
 *     </sites>
 *     // CSV データを Json データに変換する(Reader -> Writer)。
 *      * ファイル -> ファイルを Reader -> Writer に変換。
 *      * 委譲による実装。
 *     {sites:
 *       {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/}
 *       }
 *     }
 * </pre>
 * 
 * @author 2015/05/25 matsushima
 */
public class AdapterSample {

	public static void main(String[] args) {
		new AdapterSample().client();
	}
	
	void client() {
		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"
				;
		try {
			System.out.println("// CSV データの文字列を XML データの文字列に変換する(文字列 -> 文字列)。");
			System.out.print(new CsvToXmlConverter_Adaptee().conv("sites", csvText));
			
			System.out.println("// CSV データファイルを Json データファイルに変換する(ファイル -> ファイル)。");
			new CsvToJsonConverter_Adaptee().convert("sites", "urllist.csv", "urllist.json");
			try (BufferedReader br = new BufferedReader(new FileReader("urllist.json"))) {
				for (String line; null != (line = br.readLine()); ) {
					System.out.println(line);
				}
			}
			
			System.out.println("// CSV データを XML データに変換する(Reader -> Writer)。");
			System.out.println(" * 文字列 -> 文字列を Reader -> Writer に変換。");
			System.out.println(" * 継承による実装。");
			CsvConverter_Target xmlConv = new CsvToXmlConverter_Adapter();
			xmlConv.convert("sites", new StringReader(csvText), new OutputStreamWriter(System.out));
			
			System.out.println("// CSV データを Json データに変換する(Reader -> Writer)。");
			System.out.println(" * ファイル -> ファイルを Reader -> Writer に変換。");
			System.out.println(" * 委譲による実装。");
			CsvConverter_Target jsonConv = new CsvToJsonConverter_Adapter();
			jsonConv.convert("sites", new StringReader(csvText), new OutputStreamWriter(System.out));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * CSV -> XML コンバータ。
	 */
	public class CsvToXmlConverter_Adaptee {
		/**
		 * CSV データの文字列を XML データの文字列に変換する(文字列 -> 文字列)。
		 * 
		 * @param name ルートノードの名前。
		 * @param text CSV データの文字列。
		 * @return 変換後の XML データの文字列。
		 */
		public String conv(String name, String text) {
			String[] names = null;
			StringWriter result = new StringWriter();
			PrintWriter writer = new PrintWriter(result);
			writer.println("<" + name + ">");
			for (String line: text.split("\n")) {
				String[] cols = line.split(",");
				if (null == names) {
					names = cols;
				} else {
					writer.println(" <entry>");
					for (int i = 0; i < Math.min(names.length, cols.length); ++ i) {
						writer.println("  <" + names[i] + ">" + cols[i] + "</" + names[i] + ">");
					}
					writer.println(" </entry>");
				}
			}
			writer.println("</" + name + ">");
			return result.toString();
		}
	}
	
	/**
	 * CSV -> Json コンバータ。
	 */
	public class CsvToJsonConverter_Adaptee {
		/**
		 * CSV データファイルを Json データファイルに変換する(ファイル -> ファイル)。
		 * 
		 * @param name ルートノードの名前。
		 * @param srcPath CSV データファイルのパス。
		 * @param dstPath 変換後の Json ファイルのパス。
		 * @throws IOException
		 */
		public void convert(String name, String srcPath, String dstPath) throws IOException {
			try (BufferedReader reader = new BufferedReader(new FileReader(srcPath));
					PrintWriter writer = new PrintWriter(new FileWriter(dstPath))) {
				String[] names = null;
				writer.println("{" + name + ":");
				String indent1 = "  ";
				for (String line; null != (line = reader.readLine()); ) {
					String[] cols = line.split(",");
					if (null == names) {
						names = cols;
					} else {
						writer.println(indent1 + "{entry:");
						indent1 = " ,";
						String indent2 = "    ";
						for (int i = 0; i < Math.min(names.length, cols.length); ++ i) {
							writer.println(indent2 + "{" + names[i] + ": " + cols[i] + "}");
							indent2 = "   ,";
						}
						writer.println("  }");
					}
				}
				writer.println("}");
			}
		}
	}

	/**
	 * CSV コンバータのインターフェース。
	 */
	public interface CsvConverter_Target {
		/**
		 * CSV データを変換する。
		 * 
		 * @param name ルートノードの名前。
		 * @param in CSV データの入力元
		 * @param out 変換後のデータの出力先。
		 * @throws IOException
		 */
		public void convert(String name, Reader in, Writer out) throws IOException;
	}
	
	/**
	 * CSV -> XML コンバータ。
	 * 文字列 -> 文字列を Reader -> Writer に変換。
	 * 継承による実装。
	 */
	public class CsvToXmlConverter_Adapter
			extends CsvToXmlConverter_Adaptee implements CsvConverter_Target {
		/**
		 * CSV データを XML データに変換する(Reader -> Writer)。
		 * 
		 * @param name ルートノードの名前。
		 * @param in CSV データの入力元
		 * @param out 変換後の XML データの出力先。
		 * @throws IOException
		 */
		@Override
		public void convert(String name, Reader in, Writer out) throws IOException {
			// 入力データ読み込み
			StringBuilder sb = new StringBuilder();
			char[] buf = new char[1024];
			for (int read; -1 != (read = in.read(buf)); ) {
				sb.append(buf, 0, read);
			}
			// 変換処理
			String result = super.conv(name, sb.toString());
			// 出力
			out.write(result);
			out.flush();
		}
	}
	
	/**
	 * CSV -> Json コンバータ。
	 * ファイル -> ファイルを Reader -> Writer に変換。
	 * 委譲による実装。
	 */
	public class CsvToJsonConverter_Adapter implements CsvConverter_Target {
		/**
		 * CSV データを Json データに変換する(Reader -> Writer)。
		 * 
		 * @param name ルートノードの名前。
		 * @param in CSV データの入力元
		 * @param out 変換後の Json データの出力先。
		 * @throws IOException
		 */
		@Override
		public void convert(String name, Reader in, Writer out) throws IOException {
			char[] buf = new char[1024];
			// 入力データを一時ファイルに書き出し
			File inTmpFile = File.createTempFile("jsonconv_in", ".tmp");
			try (FileWriter inTmpWriter = new FileWriter(inTmpFile)) {
				for (int read; -1 != (read = in.read(buf)); ) {
					inTmpWriter.write(buf, 0, read);
				}
			}
			// 出力一時ファイル
			File outTmpFile = File.createTempFile("jsonconv_out", ".tmp");
			// 変換処理
			CsvToJsonConverter_Adaptee converter = new CsvToJsonConverter_Adaptee();
			converter.convert(name, inTmpFile.getAbsolutePath(), outTmpFile.getAbsolutePath());
			// 出力一時ファイルを読み込んで出力
			try (FileReader outTmpReader = new FileReader(outTmpFile)) {
				for (int read; -1 != (read = outTmpReader.read(buf)); ) {
					out.write(buf, 0, read);
				}
			}
			out.flush();
			// 一時ファイルを削除
			inTmpFile.delete();
			outTmpFile.delete();
		}
	}
}

0 件のコメント:

コメントを投稿