JavaでPrototypeパターン

# はじめに
[GoF](https://ja.wikipedia.org/wiki/ギャング・オブ・フォー_(情報工学))のデザインパターンを紹介している[『増補改訂版 Java言語で学ぶデザインパターン入門』](https://www.amazon.co.jp/増補改訂版Java言語で学ぶデザインパターン入門-結城-浩/dp/4797327030/ref=pd_lpo_sbs_14_t_0?_encoding=UTF8&psc=1&refRID=2ZE4GPYNN55JGDR5QMHP)を読んで、学んだ内容についてまとめます。

# Prototypeパターン
## Prototypeとは
日本語に訳すと「原型」を意味します。
この**原型となるインスタンスを用いて、他のクラスをコピー(複製)し、新しいインスタンスを作成するパターン**のことを**Prototypeパターン**と言います。
通常はインスタンスを生成する際にはnewを用いますが、Prototypeパターンではnewではなく、全てのクラスのスーパークラスである`java.lang.Object`クラスで定義されている`clone`メソッドを用いてインスタンスの作成を行います。

## 登場人物
Prototypeパターン使用するのは以下のクラス図に登場するクラスです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/278934/cbf410cb-07ea-e8f3-20fb-d6a1179a8a9c.png)

### 抽象クラス
- **Prototype**   
`createClone`メソッドを定義するクラスです。  
実装はサブクラスの`ConcreatePrototype`クラスで行います。

### 実装クラス
- **ConcreatePrototype**
スーパークラスの`Prototype`クラスで定義された`createClone`メソッドの実装を行います。

- **Client**
`createClone`メソッドを使用するクラスです。


## 具体例
具体例として、以下のクラスをもとに説明します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/278934/171ad9f0-caca-ac79-3ac6-8cdcbdbce8a4.png)

### インタフェース
- **Productインタフェース**  

```java:Product.java
package framework;

import java.lang.Cloneable;

// 1. java.lang.Cloneableを継承
public interface Product extends Cloneable {
	public abstract void display(String s);

//2. createCloneメソッドを宣言
	public abstract Product createClone();
}
```
ポイントは以下の2点です。
  **1.**`clone`メソッドでコピーできるように`java.lang.Cloneable`インタフェースを継承している。
  **2.**`createClone`メソッドを宣言している。

補足の説明を行います。
`clone`メソッドは全てのクラスのスーパークラスである`java.lang.Object`クラスで定義されています。
この`clone`メソッドを実行する場合、コピー元のクラスは`java.lang.Cloneable`インタフェースを実装している必要があります。
`java.lang.Cloneable`インタフェースを実装していない場合、`CloneNotSupportedException`の例外が発生します。
また、`createClone`メソッドに関しては`clone`メソッドを使用するためのメソッドですが、実装はサブクラスの`Surround`クラスと`UnderLine`クラスで行います。

### 実装クラス
- **Clientクラス**
  
```java:Client.java
package framework;

import java.util.*;

public class Client {
	private HashMap<String, Product> stringName = new HashMap<>();

	// 1. HashMapに登録
	public void register(String name, Product pro) {
		stringName.put(name, pro);
	}

	// 2. createCloneメソッドを使用
	public Product create(String proname) {
		Product p = (Product) stringName.get(proname);
		return p.createClone();
	}
}
```
`Client`クラスはコピーのメソッドを使用するためのクラスです。
ポイントは以下の2点です。
  **1.**`register`メソッドでHashMapにProductインタフェースを実装したインスタンスを登録している。
  **2.**`create`メソッド内で`createClone()`メソッドを使用している。

- **Surroundクラス**  

```java:Surround.java
import framework.*;

public class Surround implements Product {
	private char srchar;

	public Surround(char srchar) {
		this.srchar = srchar;
	}

	// 1. displayメソッドの実装
	@Override
	public void display(String s) {
		int length = s.getBytes().length;
		for (int i = 0; i < length + 4; i++) {
			System.out.print(srchar);
		}
		System.out.println("");
		System.out.println(srchar + " " + s + " " + srchar);
		for (int i = 0; i < length + 4; i++) {
			System.out.print(srchar);
		}
		System.out.println("\n");
	}

	// 2. createCloneメソッドの実装
	@Override
	public Product createClone() {
		Product p = null;
		try {
			p = (Product) clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return p;
	}
}
```
`Surround`クラスはコピーのメソッドの実装を行うためのクラスです。
ポイントは以下の2点です。
  **1.**コンストラクタで受け取った文字で、`display`メソッドの呼び出し時に受け取った文字列を囲んで表示する`display`メソッドの実装を行っている。
  **2.**`createClone`メソッドの実装を行っている。

2.に関して補足の説明を行います。ポイントは**2つ**です。
1つ目は**try-catch文で囲まれている**点です。
`clone`メソッドはProductインタフェースでも説明したように`java.lang.Cloneable`インタフェースを実装している必要があります。
実装していない場合、`CloneNotSupportedException`の例外が発生するので、例外が発生した場合のことを考慮してtry-catch文で囲んでいます。
2つ目は**`createClone`メソッド内で`clone`メソッドを呼び出している**点です。
`clone`メソッドを呼び出せることができるのは自分のクラス(またはサブクラス)からしか呼び出すことができません。
そのため、`Client`クラスから`clone`メソッドを呼び出すためには`createClone`メソッドのような別のメソッドを一旦経由して呼び出しを行う必要があります。

- **Underlineクラス**  

```java:Underline.java
import framework.*;

public class Underline implements Product {
	private char ulchar;

	public Underline(char ulchar) {
		this.ulchar = ulchar;
	}

	// 1. displayメソッドの実装
	@Override
	public void display(String s) {
		int length = s.getBytes().length;
		System.out.println("\"" + s + "\"");
		System.out.print(" ");
		for (int i = 0; i < length; i++) {
			System.out.print(ulchar);
		}
		System.out.println("\n");
	}

	// 2. createCloneメソッドの実装
	@Override
	public Product createClone() {
		Product p = null;
		try {
			p = (Product) clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return p;
	}
}
```
`Underline`クラスは`Surround`クラスと同様にコピーのメソッドの実装を行うためのクラスです。
ポイントは以下の2点です。
  **1.**コンストラクタで受け取った文字で、`display`メソッドの呼び出し時に受け取った文字列の下にアンダーラインのように表示する`display`メソッドの実装を行っている。
  **2.**`createClone`メソッドの実装を行っている。

補足の説明は`Surround`クラスと同様のため、割愛します。

### 実行クラス
- **Mainクラス**  

```java:Main.java
import framework.*;

public class Main {
	public static void main(String[] args) {
		// 準備
		Client manager = new Client();
		Underline ul1 = new Underline('-');
		Underline ul2 = new Underline('~');
		Surround sr1 = new Surround('#');
		Surround sr2 = new Surround('@');
		manager.register("underLine text1", ul1);
		manager.register("underLine text2", ul2);
		manager.register("surround text1", sr1);
		manager.register("surround text2", sr2);

		// 生成
		Product p1 = manager.create("underLine text1");
		p1.display("Hello, world.");
		Product p2 = manager.create("underLine text2");
		p2.display("Hello, world.");
		Product p3 = manager.create("surround text1");
		p3.display("Hello, world.");
		Product p4 = manager.create("surround text2");
		p4.display("Hello, world.");
	}
}
```
準備において`register`メソッドで`Underline`と`Surround`インスタンスの登録を行っています。
生成において`create`メソッドを呼び出してインスタンスのコピーを作成し、`display`メソッドを呼び出しています。

### 実行結果
`Main.java`を実行した結果は以下になります。
それぞれ与えられた文字で下線表示、または囲んで表示ができていることが確認できます。

```:実行結果
"Hello, world."
 -------------

"Hello, world."
 ~~~~~~~~~~~~~　

#################
# Hello, world. #
#################

@@@@@@@@@@@@@@@@@
@ Hello, world. @
@@@@@@@@@@@@@@@@@
```
※Markdownで~だけだとコードとして認識されないようで全角スペースを追記しています。
エスケープできる方法があれば教えていただければ幸いです...

**※追記　2018/11/8**
links_2_3_4さんに末尾に全角スペースを追記する方法を教えていただきました。
ありがとうございます。

## メリット
以下の3点があります。
**1.**類似のクラスのインスタンスが複数存在する際に、クラスを分ける必要がなく管理が容易である。
**2.**クラスからのインスタンス生成が難しい場合に、インスタンスの作成が容易にできる。
**3.**フレームワークと生成するインスタンスを切り分けることができる。

# まとめ
原型となるインスタンスを用いて、他のクラスをコピーし、新しいインスタンスを作成するPrototypeパターンに関して学びました。
以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。

- [Prototypeサンプルコード](https://github.com/masayuki777/study/tree/master/DesignPatterns/06_Prototype)

また、他のデザインパターンに関しては以下でまとめていますので、こちらも参考にどうぞ。

- [[随時更新]Javaでデザインパターンまとめ](https://qiita.com/mk777/items/4e13505b654fc21c76a2)

# 参考文献
- [増補改訂版 Java言語で学ぶデザインパターン入門](https://www.amazon.co.jp/増補改訂版Java言語で学ぶデザインパターン入門-結城-浩/dp/4797327030/ref=pd_lpo_sbs_14_t_0?_encoding=UTF8&psc=1&refRID=2ZE4GPYNN55JGDR5QMHP)