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

2015年6月7日日曜日

1-5. Singleton パターン

クラスが1つのインスタンスしか持たないことを保証し、それにアクセスするインターフェース
を提供します。



プログラム中でただ1つのインスタンスを複数個所で利用したい場合に使用します。

static フィールドによる実装:
クラスがロードされるときにインスタンスが生成されます。
Initialization-on-demand holder idiom による実装:
getInstance() の最初の呼び出しでインスタンスの取得時にインスタンスが生成されます。

実行結果:
    // static フィールドによる実装。
    new Date():     2015/06/07 03:35:51.700
    DateSingleton1()
    DateSingleton1: 2015/06/07 03:35:51.702
    DateSingleton1: 2015/06/07 03:35:51.702
    // Initialization-on-demand holder idiom による実装。
    new Date():     2015/06/07 03:35:51.702
    DateSingleton2()
    DateSingleton2: 2015/06/07 03:35:51.703
    DateSingleton2: 2015/06/07 03:35:51.703
package design_pattern;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Singleton
 * GoF, Code Complete
 * クラスが1つのインスタンスしか持たないことを保証し、それにアクセスするインターフェース
 * を提供します。
 * 
 * プログラム中でただ1つのインスタンスを複数個所で利用したい場合に使用します。
 * 
 * static フィールドによる実装:
 * クラスがロードされるときにインスタンスが生成されます。
 * Initialization-on-demand holder idiom による実装:
 * getInstance() の最初の呼び出しでインスタンスの取得時にインスタンスが生成されます。
 * 
 * 実行結果:
 * <pre>
 *     // static フィールドによる実装。
 *     new Date():     2015/06/07 03:35:51.700
 *     DateSingleton1()
 *     DateSingleton1: 2015/06/07 03:35:51.702
 *     DateSingleton1: 2015/06/07 03:35:51.702
 *     // Initialization-on-demand holder idiom による実装。
 *     new Date():     2015/06/07 03:35:51.702
 *     DateSingleton2()
 *     DateSingleton2: 2015/06/07 03:35:51.703
 *     DateSingleton2: 2015/06/07 03:35:51.703
 * </pre>
 * 
 * @author 2015/05/24 matsushima
 */
public class SingletonSample {
	/**
	 * main
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		SimpleDateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
		System.out.println("// static フィールドによる実装。");
		Date date11 = new Date();
		System.out.println("new Date():     " + df.format(date11));
		Date date12 = DateSingleton1.getInstance().getDate();
		System.out.println("DateSingleton1: " + df.format(date12));
		Date date13 = DateSingleton1.getInstance().getDate();
		System.out.println("DateSingleton1: " + df.format(date13));
		System.out.println("// Initialization-on-demand holder idiom による実装。");
		Date date21 = new Date();
		System.out.println("new Date():     " + df.format(date21));
		Date date22 = DateSingleton2.getInstance().getDate();
		System.out.println("DateSingleton2: " + df.format(date22));
		Date date23 = DateSingleton2.getInstance().getDate();
		System.out.println("DateSingleton2: " + df.format(date23));
	}
}

/** static フィールドによる実装。 */
final class DateSingleton1 {
	private static final DateSingleton1 instance = new DateSingleton1();
	private Date date = new Date();
	private DateSingleton1() {
		System.out.println("DateSingleton1()");
	}
	public static DateSingleton1 getInstance() {
		return DateSingleton1.instance;
	}
	public Date getDate() {
		return this.date;
	}
}

/** Initialization-on-demand holder idiom による実装。 */
final class DateSingleton2 {
	private Date date = new Date();
	private DateSingleton2() {
		System.out.println("DateSingleton2()");
	}
	private static class DateSingletonHolder {
		private static final DateSingleton2 instance = new DateSingleton2();
	}
	public static DateSingleton2 getInstance() {
		return DateSingletonHolder.instance;
	}
	public Date getDate() {
		return this.date;
	}
}

2015年6月6日土曜日

1-4. Prototype パターン

プロトタイプとなるインスタンスを用いて作成するオブジェクトの種類を指定し、
このプロトタイプを複製することで新しいオブジェクトを生成します。



一般的にインスタンスの生成は、クラス名を指定して new し、必要に応じて初期化を行い
ます。
Prototype パターンでは、上記で生成したプロトタイプを複製し、必要に応じて
最小限の初期化することでインスタンスの生成を行います。
これにより、以下のメリットがあります。
・初期化処理のコストが高いときにそれを軽減できる
・基本クラスのインターフェースから複製を行う場合、実際の複製処理が派生クラスで
行われるため、直接派生クラスを指定する必要がない

一般的にインスタンスを生成するには、直接クラス名を指定して new します。
    ConcreteProduct product = new ConcreteProduct();
    product.init(); // 時間のかかる処理
Prototype パターンでは、あらかじめプロトタイプを作成しておき、それを複製することで
インスタンスの生成を行います。
    // プロトタイプを作成
    AbstractProduct prototype = new ConcreteProduct();
    prototype.init(); // 時間のかかる処理
    // プロトタイプからインスタンスを生成
    AbstractProduct product = prototype.clone();
    prototype.setValue(100); // 時間のかからない処理
これにより、複製処理のコストを軽減できます。
また上記では、基本クラス AbstractProduct#clone() を呼び出して複製を行っていますが、
実際の複製処理は派生クラス ConcreteProduct で行われます。これにより、
prototype を作成しておけば複製処理では実際の派生クラスを意識する必要がありません。

サンプルプログラムは、プロトタイプからインスタンスを生成するクライアントクラスを
介して、インスタンスを生成する例です。
出力結果:
    0 1 2 3 4 5 6 7 8 9 
package design_pattern;

/**
 * Prototype
 * GoF
 * プロトタイプとなるインスタンスを用いて作成するオブジェクトの種類を指定し、
 * このプロトタイプを複製することで新しいオブジェクトを生成します。
 * 
 * 一般的にインスタンスの生成は、クラス名を指定して new し、必要に応じて初期化を行い
 * ます。
 * Prototype パターンでは、上記で生成したプロトタイプを複製し、必要に応じて
 * 最小限の初期化することでインスタンスの生成を行います。
 * これにより、以下のメリットがあります。
 * ・初期化処理のコストが高いときにそれを軽減できる
 * ・基本クラスのインターフェースから複製を行う場合、実際の複製処理が派生クラスで
 * 行われるため、直接派生クラスを指定する必要がない
 * 
 * 一般的にインスタンスを生成するには、直接クラス名を指定して new します。
 * <pre>
 *     ConcreteProduct product = new ConcreteProduct();
 *     product.init(); // 時間のかかる処理
 * </pre>
 * Prototype パターンでは、あらかじめプロトタイプを作成しておき、それを複製することで
 * インスタンスの生成を行います。
 * <pre>
 *     // プロトタイプを作成
 *     AbstractProduct prototype = new ConcreteProduct();
 *     prototype.init(); // 時間のかかる処理
 *     // プロトタイプからインスタンスを生成
 *     AbstractProduct product = prototype.clone();
 *     prototype.setValue(100); // 時間のかからない処理
 * </pre>
 * これにより、複製処理のコストを軽減できます。
 * また上記では、基本クラス AbstractProduct#clone() を呼び出して複製を行っていますが、
 * 実際の複製処理は派生クラス ConcreteProduct で行われます。これにより、
 * prototype を作成しておけば複製処理では実際の派生クラスを意識する必要がありません。
 * 
 * サンプルプログラムは、プロトタイプからインスタンスを生成するクライアントクラスを
 * 介して、インスタンスを生成する例です。
 * 出力結果:
 * <pre>
 *     0 1 2 3 4 5 6 7 8 9 
 * </pre>
 * 
 * @author 2015/05/24 matsushima
 */
public class PrototypeSample {
	/**
	 * main
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		new PrototypeSample().client();
	}

	/**
	 * Client
	 */
	void client() {
		try {
			Client client = new Client(new ConcreatePrototype()); // プロトタイプ設定。
			for (int i = 0; i < 10; ++ i) {
				Prototype copy = client.copy(); // プロトタイプを複製してインスタンスを生成。
				copy.setValue(i);
				System.out.print(copy.getValue() + " ");
			}
			System.out.println();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
	}

	/** プロトタイプの抽象クラス。 */
	abstract class Prototype implements Cloneable {
		@Override
		public Object clone() throws CloneNotSupportedException {
			return super.clone(); // 実際の複製処理
		}
		public void init() {
			// 時間のかかる処理
		}
		public abstract void setValue(int value);
		public abstract int getValue();
	}

	/** プロトタイプクラス。 */
	class ConcreatePrototype extends Prototype {
		private int value;
		@Override
		public void setValue(int value) {
			this.value = value;
		}
		@Override
		public int getValue() {
			return this.value;
		}
	}

	/** プロトタイプからインスタンスを生成するクライアントクラス。 */
	class Client {
		private Prototype prototype;
		/** プロトタイプ設定。 */
		public Client(Prototype prototype) {
			this.prototype = prototype;
		}
		/** プロトタイプを複製してインスタンスを生成。 */
		public Prototype copy() throws CloneNotSupportedException {
			return (Prototype)prototype.clone();
		}
	}
}

1-3. Factory Method パターン

オブジェクトを生成するインターフェースを定義するが、どのクラスをインスタンス化するかは
サブクラスが決定します。



Creator 抽象クラスで Product の生成を行う factory method インターフェースを用意し、
ConcreteCreator 派生クラスで生成を実装します。
これにより、ConcreteFactory を差し替えるだけで別の Product を得ることができます。

一般的に関連するインスタンス群を生成するには、直接クラス名を指定して new します。
ConcreteProduct product = new ConcreteProduct();
Factory Method パターンでは、Creator 抽象クラスでこれらを行うインターフェースを
用意し、ConcreteCreator 派生クラスで実装します。
public abstract class Creator {
        protected abstract AbstractProduct factoryMethod();
        public void anOperation() {
            AbstractProduct product = this.factoryMethod();
            doSomething(product);
        }
    }
    public class ConcreteCreator extends Creator {
        protected AbstractProduct factoryMethod() {
            return new ConcreteProduct();
        }
    }
上記の例では Creator#anOperation(Template Method パターンの template method) 内で
ConcreteFactory#factoryMethod(factory method) で生成された Product に対して操作を
行っています。
そのほかにも、factory method を public にして、外部から呼び出す方法もあります。

サンプルプログラムは、入力されたテキストを解析し、XML と Json 形式で出力する例です。
出力結果:
<PC><CPU><name>Core i7</name><clock>3GHz</clock></CPU><Memory><name>DDR3</name><capacity>4GB</capacity></Memory></PC>
    {PC:{CPU:{name:Core i7},{clock:3GHz}},{Memory:{name:DDR3},{capacity:4GB}}}
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;

/**
 * Factory Method
 * GoF, Code Complete
 * オブジェクトを生成するインターフェースを定義するが、どのクラスをインスタンス化するかは
 * サブクラスが決定します。
 * 
 * Creator 抽象クラスで Product の生成を行う factory method インターフェースを用意し、
 * ConcreteCreator 派生クラスで生成を実装します。
 * これにより、ConcreteFactory を差し替えるだけで別の Product を得ることができます。
 * 
 * 一般的に関連するインスタンス群を生成するには、直接クラス名を指定して new します。
 * <pre>
 *     ConcreteProduct product = new ConcreteProduct();
 * </pre>
 * Factory Method パターンでは、Creator 抽象クラスでこれらを行うインターフェースを
 * 用意し、ConcreteCreator 派生クラスで実装します。
 * <pre>
 *     public abstract class Creator {
 *         protected abstract AbstractProduct factoryMethod();
 *         public void anOperation() {
 *             AbstractProduct product = this.factoryMethod();
 *             doSomething(product);
 *         }
 *     }
 *     public class ConcreteCreator extends Creator {
 *         protected AbstractProduct factoryMethod() {
 *             return new ConcreteProduct();
 *         }
 *     }
 * </pre>
 * 上記の例では Creator#anOperation(Template Method パターンの template method) 内で
 * ConcreteFactory#factoryMethod(factory method) で生成された Product に対して操作を
 * 行っています。
 * そのほかにも、factory method を public にして、外部から呼び出す方法もあります。
 * 
 * サンプルプログラムは、入力されたテキストを解析し、XML と Json 形式で出力する例です。
 * 出力結果:
 * <pre>
 *     <PC><CPU><name>Core i7</name><clock>3GHz</clock></CPU><Memory><name>DDR3</name><capacity>4GB</capacity></Memory></PC>
 *     {PC:{CPU:{name:Core i7},{clock:3GHz}},{Memory:{name:DDR3},{capacity:4GB}}}
 * </pre>
 * 
 * @author 2015/05/23 matsushima
 */
public class FactoryMethodSample {
	/**
	 * main
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		for (String type: args.length >= 1 ? args : new String[]{"xml", "json"}) {
			new FactoryMethodSample().client(type);
		}
	}
	/**
	 * Client
	 */
	void client(String type) {
		String text =
				"PC:\n"
				+ " CPU:\n"
				+ "  name:\n"
				+ "   Core i7\n"
				+ "  clock:\n"
				+ "   3GHz\n"
				+ " Memory:\n"
				+ "  name:\n"
				+ "   DDR3\n"
				+ "  capacity:\n"
				+ "   4GB\n"
				;
		try {
			Creator creator;
			switch (type.toLowerCase()) {
			case "xml": creator = new XmlDocument_ConcreteCreator(); break;
			case "json": creator = new JsonDocument_ConcreteCreator(); break;
			default: throw new UnsupportedOperationException(type);
			}
			creator.parse(new StringReader(text));
			creator.printNode(System.out);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	// Creator ///////////////////////////////////////
	/** Creator 抽象クラス。 */
	public abstract class Creator extends AbstractNode_AbstractProduct {
		protected Creator(String name, String value) { super(name, value); }
		public void parse(Reader in) throws IOException {
			LinkedList<Node_AbstractProduct> parentStack = new LinkedList<>();
			parentStack.push(this);
			BufferedReader reader = new BufferedReader(in);
			int indent = -1;
			String line;
			while (null != (line = reader.readLine())) {
				String nodeText = line.trim();
				// 親を探す
				Node_AbstractProduct parent;
				for (int i = 0; i < indent - (line.length() - nodeText.length()) + 1; ++ i) {
					parent = parentStack.pop();
				}
				parent = parentStack.peek();
				// 子供を追加
				Node_AbstractProduct node = parent.appendChild(nodeText.endsWith(":")
						? this.createElement(nodeText.substring(0, nodeText.length() - 1))
						: this.createTextNode(nodeText));
				parentStack.push(node);
				indent = line.length() - nodeText.length();
			}
		}
		public void printNode(PrintStream writer) {
			this.getChildNodes().get(0).printNode(writer);
			writer.println();
		}
		protected abstract Node_AbstractProduct createElement(String name);
		protected abstract Node_AbstractProduct createTextNode(String value);
	}
	/** XML Node を生成する Document Creator。 */
	public class XmlDocument_ConcreteCreator extends Creator {
		public XmlDocument_ConcreteCreator() { super("XML", null); }
		protected Node_AbstractProduct createElement(String name) {
			return new XmlElement_ConcreteProduct(name);
		}
		protected Node_AbstractProduct createTextNode(String value) {
			return new XmlText_ConcreteProduct(value);
		}
	}
	/** Json Node を生成する Document Creator。 */
	public class JsonDocument_ConcreteCreator extends Creator {
		public JsonDocument_ConcreteCreator() { super("Json", null); }
		protected Node_AbstractProduct createElement(String name) {
			return new JsonElement_ConcreteProduct(name);
		}
		protected Node_AbstractProduct createTextNode(String value) {
			return new JsonText_ConcreteProduct(value);
		}
	}

	// Product ///////////////////////////////////////
	/** Node Product インターフェース。 */
	public interface Node_AbstractProduct {
		public String getNodeName();
		public String getNodeValue();
		public Node_AbstractProduct appendChild(Node_AbstractProduct node);
		public List<Node_AbstractProduct> getChildNodes();
		public void printNode(PrintStream writer);
	}
	/** Node Product 抽象クラス。 */
	public abstract class AbstractNode_AbstractProduct implements Node_AbstractProduct {
		private String name, value;
		private LinkedList<Node_AbstractProduct> children = new LinkedList<>();
		protected AbstractNode_AbstractProduct(String name, String value) {
			this.name = name;
			this.value = value;
		}
		public String getNodeName() { return this.name; }
		public String getNodeValue() { return this.value; }
		public Node_AbstractProduct appendChild(Node_AbstractProduct node) {
			this.children.add(node);
			return node;
		}
		public List<Node_AbstractProduct> getChildNodes() {
			return this.children;
		}
	}
	/** XML Element Node Product。 */
	public class XmlElement_ConcreteProduct extends AbstractNode_AbstractProduct {
		public XmlElement_ConcreteProduct(String name) { super(name, null); }
		public void printNode(PrintStream writer) {
			writer.print("<" + this.getNodeName() + ">");
			for (Node_AbstractProduct node: this.getChildNodes()) {
				node.printNode(writer);
			}
			writer.print("</" + this.getNodeName() + ">");
		}
	}
	/** XML Text Node Product。 */
	public class XmlText_ConcreteProduct extends AbstractNode_AbstractProduct {
		public XmlText_ConcreteProduct(String value) { super(null, value); }
		public void printNode(PrintStream writer) {
			writer.print(this.getNodeValue());
		}
	}
	/** Json Element Node Product。 */
	public class JsonElement_ConcreteProduct extends AbstractNode_AbstractProduct {
		public JsonElement_ConcreteProduct(String name) { super(name, null); }
		public void printNode(PrintStream writer) {
			writer.print("{" + this.getNodeName() + ":");
			String comma = "";
			for (Node_AbstractProduct node: this.getChildNodes()) {
				writer.print(comma);
				comma = ",";
				node.printNode(writer);
			}
			writer.print("}");
		}
	}
	/** Json Text Node Product。 */
	public class JsonText_ConcreteProduct extends AbstractNode_AbstractProduct {
		public JsonText_ConcreteProduct(String value) { super(null, value); }
		public void printNode(PrintStream writer) {
			writer.print(this.getNodeValue());
		}
	}
}

2015年6月3日水曜日

1-2. Builder パターン

同じ構築プロセスで別の表現を作成できるように、複合したオブジェクトの構築を分離します。



Builder パターンでは、Product を構成するオブジェクト Part を生成する Builder を用いて、
Director がオブジェクトの構築を行います。
Builder パターンの目的は、オブジェクトの生成・構築を Builder, Director に分離すること
にあります。
これにより、Builder を差し替えるだけで別の内容の Product を得ることが可能に
なります。

複数のオブジェクトを構築する場合、
Product product = new Product();
    product.add(new Part1());
    product.add(new Part2());
Builder パターンでは、Builder 抽象クラスで個々の Part オブジェクトを生成する
インターフェースを用意し、ConcreteBuilder 派生クラスで実装します。
public interface Builder {
        public void buildPart1();
        public void buildPart2();
    }
    public class ConcreteBuilder1 implements Builder {
        private Product product;
        public ConcreteBuilder1(Product product) { this.product = product; }
        public void buildPart1() { this.product.add(new Part1()); }
        public void buildPart2() { this.product.add(new Part2()); }
        public Product getResult() { return this.product; }
    }
Director クラス内で、これらの Builder のインターフェースを介して実際に
オブジェクトを構築します。
public class Director {
        private Builder builder;
        public Director(Builder builder) { this.builder = builder; }
        public void construct() {
            this.builder.buildPart1();
            this.builder.buildPart2();
        }
    }
Client は、Builder, Director インターフェースを介してオブジェクトを
構築・取得します。
Builder builder = new ConcreateBuilder1();
    Director director = new Director(builder);
    director.construct();
    Product product = builder.getResult();
これにより、Builder を差し替えるだけで別の内容の Product を得ることが可能に
なります。
Builder builder = new ConcreateBuilder2(); // 差し替え
    Director director = new Director(builder);
    director.construct();
    Product product = builder.getResult();

サンプルプログラムでは、異なる構成部品からなる PC を XML, Json で構築する
Document Product の構築例を示します。
package design_pattern;

import java.io.PrintStream;
import java.util.LinkedList;
import java.util.List;

/**
 * Builder
 * GoF
 * 同じ構築プロセスで別の表現を作成できるように、複合したオブジェクトの構築を分離します。
 * 
 * Builder パターンでは、Product を構成するオブジェクト Part を生成する Builder を用いて、 
 * Director がオブジェクトの構築を行います。
 * Builder パターンの目的は、オブジェクトの生成・構築を Builder, Director に分離すること
 * にあります。
 * これにより、Builder を差し替えるだけで別の内容の Product を得ることが可能に
 * なります。
 * 
 * 複数のオブジェクトを構築する場合、
 * <pre>
 *     Product product = new Product();
 *     product.add(new Part1());
 *     product.add(new Part2());
 * </pre>
 * Builder パターンでは、Builder 抽象クラスで個々の Part オブジェクトを生成する
 * インターフェースを用意し、ConcreteBuilder 派生クラスで実装します。
 * <pre>
 *     public interface Builder {
 *         public void buildPart1();
 *         public void buildPart2();
 *     }
 *     public class ConcreteBuilder1 implements Builder {
 *         private Product product;
 *         public ConcreteBuilder1(Product product) { this.product = product; }
 *         public void buildPart1() { this.product.add(new Part1()); }
 *         public void buildPart2() { this.product.add(new Part2()); }
 *         public Product getResult() { return this.product; }
 *     }
 * </pre>
 * Director クラス内で、これらの Builder のインターフェースを介して実際に
 * オブジェクトを構築します。
 * <pre>
 *     public class Director {
 *         private Builder builder;
 *         public Director(Builder builder) { this.builder = builder; }
 *         public void construct() {
 *             this.builder.buildPart1();
 *             this.builder.buildPart2();
 *         }
 *     }
 * </pre>
 * Client は、Builder, Director インターフェースを介してオブジェクトを
 * 構築・取得します。
 * <pre>
 *     Builder builder = new ConcreateBuilder1();
 *     Director director = new Director(builder);
 *     director.construct();
 *     Product product = builder.getResult();
 * </pre>
 * これにより、Builder を差し替えるだけで別の内容の Product を得ることが可能に
 * なります。
 * <pre>
 *     Builder builder = new ConcreateBuilder2(); // 差し替え
 *     Director director = new Director(builder);
 *     director.construct();
 *     Product product = builder.getResult();
 * </pre>
 * 
 * サンプルプログラムでは、異なる構成部品からなる PC を XML, Json で構築する
 * Document Product の構築例を示します。
 * 
 * 出力結果:
// Builder 未使用
<PC>
  <CPU>
    <name>
      Core i7
    </name>
    <clock>
      3GHz
    </clock>
  </CPU>
  <Memory>
    <name>
      DDR3
    </name>
    <capacity>
      4GB
    </capacity>
  </Memory>
</PC>
// Builder 使用
<PC>
  <CPU>
    <name>
      Core i7
    </name>
    <clock>
      3GHz
    </clock>
  </CPU>
  <Memory>
    <name>
      DDR3
    </name>
    <capacity>
      4GB
    </capacity>
  </Memory>
</PC>
// Builder 未使用
{PC:
  {CPU:
    {name:
      Core i5
    }
    ,{clock:
      2GHz
    }
  }
  ,{Memory:
    {name:
      DDR3
    }
    ,{capacity:
      2GB
    }
  }
}
// Builder 使用
{PC:
  {CPU:
    {name:
      Core i5
    }
    ,{clock:
      2GHz
    }
  }
  ,{Memory:
    {name:
      DDR3
    }
    ,{capacity:
      2GB
    }
  }
}
 * 
 * @author 2015/05/22 matsushima
 */
public class BuilderSample {
	/**
	 * main
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		for (String type: args.length >= 1 ? args : new String[]{"xml", "json"}) {
			new BuilderSample().client(type);
		}
	}
	/**
	 * Client
	 */
	void client(String type) {
		/*
		 * 複数のオブジェクトを構築する場合、
		 */
		System.out.println("// Builder 未使用");
		if ("xml".equals(type)) {
			Node_AbstractPart e1, e2, e3;
			XmlDocument_ConcreteProduct doc1 = new XmlDocument_ConcreteProduct("XML");
			doc1.appendChild(e1 = new XmlElement_ConcretePart("PC"));
			e1.appendChild(e2 = new XmlElement_ConcretePart("CPU"));
			e2.appendChild(e3 = new XmlElement_ConcretePart("name"));
			e3.appendChild(new XmlText_ConcretePart("Core i7"));
			e2.appendChild(e3 = new XmlElement_ConcretePart("clock"));
			e3.appendChild(new XmlText_ConcretePart("3GHz"));
			e1.appendChild(e2 = new XmlElement_ConcretePart("Memory"));
			e2.appendChild(e3 = new XmlElement_ConcretePart("name"));
			e3.appendChild(new XmlText_ConcretePart("DDR3"));
			e2.appendChild(e3 = new XmlElement_ConcretePart("capacity"));
			e3.appendChild(new XmlText_ConcretePart("4GB"));
			doc1.printNode(System.out, "");
		} else if ("json".equals(type)) {
			Node_AbstractPart e1, e2, e3;
			JsonDocument_ConcreteProduct doc2 = new JsonDocument_ConcreteProduct("Json");
			doc2.appendChild(e1 = new JsonElement_ConcretePart("PC"));
			e1.appendChild(e2 = new JsonElement_ConcretePart("CPU"));
			e2.appendChild(e3 = new JsonElement_ConcretePart("name"));
			e3.appendChild(new JsonText_ConcretePart("Core i5"));
			e2.appendChild(e3 = new JsonElement_ConcretePart("clock"));
			e3.appendChild(new JsonText_ConcretePart("2GHz"));
			e1.appendChild(e2 = new JsonElement_ConcretePart("Memory"));
			e2.appendChild(e3 = new JsonElement_ConcretePart("name"));
			e3.appendChild(new JsonText_ConcretePart("DDR3"));
			e2.appendChild(e3 = new JsonElement_ConcretePart("capacity"));
			e3.appendChild(new JsonText_ConcretePart("2GB"));
			doc2.printNode(System.out, "");
		} else {
			throw new UnsupportedOperationException(type);
		}
		/*
		 * Builder パターンでは、Builder 抽象クラスで個々の Part オブジェクトを生成する
		 * インターフェースを用意し、ConcreteBuilder 派生クラスで実装します。
		 * Director クラス内で、これらの Builder のインターフェースを介して実際に
		 * オブジェクトを構築します。
		 * Client は、Builder, Director インターフェースを介してオブジェクトを
		 * 構築・取得します。
		 * これにより、Builder を差し替えるだけで別の内容の Product を得ることが可能に
		 * なります。
		 */
		System.out.println("// Builder 使用");
		Builder builder;
		if ("xml".equals(type)) {
			builder = new XmlCorei7PcConCreateBuilder("XML");
		} else if ("json".equals(type)) {
			builder = new JsonCorei5PcConCreateBuilder("Json");
		} else {
			throw new UnsupportedOperationException(type);
		}
		Director director = new Director(builder);
		director.construct();
		builder.getResult().printNode(System.out, "");
	}

	// Director ///////////////////////////////////////
	/** Node(Text, Element) を生成する Director。 */
	class Director {
		private Builder builder;
		public Director(Builder builder) {
			this.builder = builder;
		}
		public void construct() {
			this.builder.buildPc();
			this.builder.buildCpu();
			this.builder.buildMemory();
		}
	}

	// Builder ///////////////////////////////////////
	/** Builder インターフェース。 */
	public interface Builder {
		public void buildPc();
		public void buildCpu();
		public void buildMemory();
		public Node_AbstractPart getResult();
	}
	/** XML Node(Core i7 PC) を生成する Builder。 */
	public class XmlCorei7PcConCreateBuilder implements Builder {
		private Node_AbstractPart document;
		public XmlCorei7PcConCreateBuilder(String name) {
			this.document = new XmlDocument_ConcreteProduct(name);
		}
		public void buildPc() {
			this.document.appendChild(new XmlElement_ConcretePart("PC"));
		}
		public void buildCpu() {
			Node_AbstractPart e1, e2, e3;
			e1 = this.document.getChildNodes().get(0);
			e1.appendChild(e2 = new XmlElement_ConcretePart("CPU"));
			e2.appendChild(e3 = new XmlElement_ConcretePart("name"));
			e3.appendChild(new XmlText_ConcretePart("Core i7"));
			e2.appendChild(e3 = new XmlElement_ConcretePart("clock"));
			e3.appendChild(new XmlText_ConcretePart("3GHz"));
		}
		public void buildMemory() {
			Node_AbstractPart e1, e2, e3;
			e1 = this.document.getChildNodes().get(0);
			e1.appendChild(e2 = new XmlElement_ConcretePart("Memory"));
			e2.appendChild(e3 = new XmlElement_ConcretePart("name"));
			e3.appendChild(new XmlText_ConcretePart("DDR3"));
			e2.appendChild(e3 = new XmlElement_ConcretePart("capacity"));
			e3.appendChild(new XmlText_ConcretePart("4GB"));
		}
		public Node_AbstractPart getResult() {
			return this.document;
		}
	}
	/** Json Node(Core i5 PC) を生成する Builder。 */
	public class JsonCorei5PcConCreateBuilder implements Builder {
		private Node_AbstractPart document;
		public JsonCorei5PcConCreateBuilder(String name) {
			this.document = new JsonDocument_ConcreteProduct(name);
		}
		public void buildPc() {
			this.document.appendChild(new JsonElement_ConcretePart("PC"));
		}
		public void buildCpu() {
			Node_AbstractPart e1, e2, e3;
			e1 = this.document.getChildNodes().get(0);
			e1.appendChild(e2 = new JsonElement_ConcretePart("CPU"));
			e2.appendChild(e3 = new JsonElement_ConcretePart("name"));
			e3.appendChild(new JsonText_ConcretePart("Core i5"));
			e2.appendChild(e3 = new JsonElement_ConcretePart("clock"));
			e3.appendChild(new JsonText_ConcretePart("2GHz"));
		}
		public void buildMemory() {
			Node_AbstractPart e1, e2, e3;
			e1 = this.document.getChildNodes().get(0);
			e1.appendChild(e2 = new JsonElement_ConcretePart("Memory"));
			e2.appendChild(e3 = new JsonElement_ConcretePart("name"));
			e3.appendChild(new JsonText_ConcretePart("DDR3"));
			e2.appendChild(e3 = new JsonElement_ConcretePart("capacity"));
			e3.appendChild(new JsonText_ConcretePart("2GB"));
		}
		public Node_AbstractPart getResult() {
			return this.document;
		}
	}

	// Product ///////////////////////////////////////
	/** Node Part インターフェース。 */
	public interface Node_AbstractPart {
		public String getNodeName();
		public String getNodeValue();
		public Node_AbstractPart appendChild(Node_AbstractPart node);
		public List<Node_AbstractPart> getChildNodes();
		public void printNode(PrintStream writer, String indent);
	}
	/** Node Part 抽象クラス。 */
	public abstract class AbstractNode_AbstractPart implements Node_AbstractPart {
		private String name, value;
		private LinkedList<Node_AbstractPart> children = new LinkedList<>();
		protected AbstractNode_AbstractPart(String name, String value) {
			this.name = name;
			this.value = value;
		}
		public String getNodeName() { return this.name; }
		public String getNodeValue() { return this.value; }
		public Node_AbstractPart appendChild(Node_AbstractPart node) {
			this.children.add(node);
			return node;
		}
		public List<Node_AbstractPart> getChildNodes() {
			return this.children;
		}
	}
	/** XML Document Product。 */
	public class XmlDocument_ConcreteProduct extends AbstractNode_AbstractPart {
		protected XmlDocument_ConcreteProduct(String name) { super(name, null); }
		public void printNode(PrintStream writer, String indent) {
			this.getChildNodes().get(0).printNode(writer, "");
		}
	}
	/** XML Element Node Part。 */
	public class XmlElement_ConcretePart extends AbstractNode_AbstractPart {
		public XmlElement_ConcretePart(String name) { super(name, null); }
		public void printNode(PrintStream writer, String indent) {
			writer.println(indent + "<" + this.getNodeName() + ">");
			for (Node_AbstractPart node: this.getChildNodes()) {
				node.printNode(writer, indent + "  ");
			}
			writer.println(indent + "</" + this.getNodeName() + ">");
		}
	}
	/** XML Text Node Part。 */
	public class XmlText_ConcretePart extends AbstractNode_AbstractPart {
		public XmlText_ConcretePart(String value) { super(null, value); }
		public void printNode(PrintStream writer, String indent) {
			writer.println(indent + this.getNodeValue());
		}
	}
	/** Json Document Product。 */
	public class JsonDocument_ConcreteProduct extends AbstractNode_AbstractPart {
		protected JsonDocument_ConcreteProduct(String name) { super(name, null); }
		public void printNode(PrintStream writer, String indent) {
			this.getChildNodes().get(0).printNode(writer, "");
		}
	}
	/** Json Element Node Part。 */
	public class JsonElement_ConcretePart extends AbstractNode_AbstractPart {
		public JsonElement_ConcretePart(String name) { super(name, null); }
		public void printNode(PrintStream writer, String indent) {
			writer.println(indent + "{" + this.getNodeName() + ":");
			indent = indent.replace(",", "");
			for (Node_AbstractPart node: this.getChildNodes()) {
				node.printNode(writer,
						indent + (this.getChildNodes().get(0) == node ? "  " : "  ,"));
			}
			writer.println(indent + "}");
		}
	}
	/** Json Text Node Part。 */
	public class JsonText_ConcretePart extends AbstractNode_AbstractPart {
		public JsonText_ConcretePart(String value) { super(null, value); }
		public void printNode(PrintStream writer, String indent) {
			writer.println(indent + this.getNodeValue());
		}
	}
}