Kotlinでデザインパターン Prototype編

## はじめに

本記事は[Java言語で学ぶデザインパターン入門](http://www.hyuki.com/dp/)を参考にしながら
JavaではなくKotlinで実装してみようというものです。

![Java言語で学ぶデザインパターン入門](http://www.hyuki.com/images/dp1.png)

### 調べてみる

#### 目的
* 予め生成したインスタンスから新たなインスタンスを作成するパターンがPrototypeです。
 （簡単に言えばインスタンスをコピー（クローン）するためのパターンです。）

* このパターンを適応するとインスタンスからコピーを作成しますので、
  GUIアプリケーションなどで図形を複製する場合などに使えます。

* もちろんクラスからインスタンスを生成することも可能ではありますが、
  インスタンスをそのままコピーするほうが色々手間が省けるよねってときにPrototypeを利用します。

#### 構成

| クラス名 | 説明 |
| ------  | --- |
| Client | Protoypeを使ってインスタンスをコピーするクラス |
| Prototype | どのようなメソッドでインスタンスをコピーするか決めるインタフェース | 
| ConcreatePrototype | Prototypeインタフェースのメソッドに合わせて、実際にどうやってコピーするか実装するクラス | 

![Prototype.png](https://qiita-image-store.s3.amazonaws.com/0/79387/d09cdf64-35f0-af2c-893f-2ca74d53bf40.png)

### 実装してみる

#### Javaだとどうだたったか？

JavaですとClonableというマーカインタフェースを継承し、
継承したクラスでCloneを正常に呼び出せるようになりShallow Copyでコピーできるようになります。

Clonableを継承しないクラスでもCloneを呼び出すことはできますが、
”CloneNotSupportedException”が発生しますので正常にコピーできません。

"Java言語で学ぶデザインパターン入門"にならってJavaで実装すると次のような感じです。

##### Product

Cloneableを継承したProductクラスを用意します。
あとは外部クラスからCloneを実行するためにcreateCloneを用意しておきます。

```java
public interface Product extends Cloneable {
    public abstract Product createClone();
}
```

##### ProductDefault

Productクラスを実装したProductDefaultクラスを作成します。
createCloneではcloneを呼び出してインスタンスをShallow Copyします。

先程説明したとおりCloneableを継承してないと
"CloneNotSupportedException"が発生するので例外をCatchしておきます。


```java
public class ProductDefault implements Product {

    public ProductDefault() { }

    public Product createClone() {
        Product p = null;
        try {
            p = (Product)clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }
}
```

##### Main

ProductDefaultクラスの動作を確認してみますと、
違うインスタンスが作成できていますので正常に動作しているようです。

```java
public class Main {
    public static void main(String[] args) {
        ProductDefault p = new ProductDefault();
        Product c = p.createClone();
        System.out.println("-- Clone Test --");
        System.out.println("pとpは" + ((p == p) ? ("同じ") : ("別の"))+ "インスタンス");
        System.out.println("pとcは" + ((p == c) ? ("同じ") : ("別の"))+ "インスタンス");
    }
}

-- Clone Test --
pとpは同じインスタンス
pとcは別のインスタンス
p:1625635731 c:1580066828
```

#### Kotlinだとどうするか？

KotlinでもJavaと同様にCloneableを継承すればCloneできるようになります。
Javaと同じようにKotlinでも実装していきます。

##### Product

```kotlin 
abstract class Product : Cloneable {
    abstract fun createClone() : Product
}
```

##### ProductDefault

```kotlin
class ProductDefault : Product() {
    override fun createClone(): ProductDefault {
        try {
            return clone() as ProductDefault
        } catch (e : CloneNotSupportedException) {
            throw e
        }
    }
}
```

##### Main

```kotlin
fun main(args: Array<String>) {
    val p = ProductDefault()
    val c = p.createClone()
    println("-- Class Clone Test --")
    println("pとpは${if (p === p) "同じ" else "別の"}インスタンス")
    println("pとcは${if (p === c) "同じ" else "別の"}インスタンス")
}

-- Class Clone Test --
pとpは同じインスタンス
pとcは別のインスタンス
```

#### KotlinだとData ClassのCopyがあるのでそっちも使える

KotlinですとData Classがサポートされています。
Data Classであればcopyがあるのでこれをインスタンスのコピーに使えます。
ですからKotlinですとProrotypeを使べきシチュエーションが少なくなっているかもしれません。

```kotlin
data class ProductData(val name : String, val serial : String)

```kotlin
fun main(args: Array<String>) {
    val data = ProductData("iPhone", "0000-1111-2222-3333")
    val copyData = data.copy()
    println("-- Data Class Clone Test --")
    println("dataとdataは${if (data === data) "同じ" else "別の"}インスタンス")
    println("dataとcopyDataは${if (data === copyData) "同じ" else "別の"}インスタンス")
}

-- Data Class Clone Test --
dataとdataは同じインスタンス
dataとcopyDataは別のインスタンス
```



## おわりに

* インスタンスをコピーしたいときはCloneableを継承したクラスを実装する
* Cloneable継承クラスでしかCloneが呼び出せないので、外部クラスがCloneできるようにメソッドを生やす
* Cloneで実行されるコピーはShallow Copyなので注意が必要、Deep Copyするならば別途実装が必要になる

## 余談:ちょっとわからなかったところ

ちなみにKotlinでProductをinterfaceで実装しようとするとNoClassDefFoundErrorが発生します。
おそらくCloneableを継承するのはクラスでなければならないみたいです。
この内容を深掘りすると長そうなのと、筆者の理解がまだ追いついていないので本記事では省略します。

```kotlin
interface Product : Cloneable {
    fun createClone() : Product
}
```

```
Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Cloneable$DefaultImpls
	at main.Product$DefaultImpls.clone(Product.kt)
	at main.ProductDefault.clone(ProductDefault.kt:3)
	at main.ProductDefault.createClone(ProductDefault.kt:6)
	at MainKt.main(main.kt:6)
Caused by: java.lang.ClassNotFoundException: java.lang.Cloneable$DefaultImpls
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 4 more
```