はじめに
本記事はJava言語で学ぶデザインパターン入門を参考にしながら
JavaではなくKotlinで実装してみようというものです。
調べてみる
目的
-
予め生成したインスタンスから新たなインスタンスを作成するパターンがPrototypeです。
(簡単に言えばインスタンスをコピー(クローン)するためのパターンです。) -
このパターンを適応するとインスタンスからコピーを作成しますので、
GUIアプリケーションなどで図形を複製する場合などに使えます。 -
もちろんクラスからインスタンスを生成することも可能ではありますが、
インスタンスをそのままコピーするほうが色々手間が省けるよねってときにPrototypeを利用します。
構成
クラス名 | 説明 |
---|---|
Client | Protoypeを使ってインスタンスをコピーするクラス |
Prototype | どのようなメソッドでインスタンスをコピーするか決めるインタフェース |
ConcreatePrototype | Prototypeインタフェースのメソッドに合わせて、実際にどうやってコピーするか実装するクラス |
実装してみる
Javaだとどうだたったか?
JavaですとClonableというマーカインタフェースを継承し、
継承したクラスでCloneを正常に呼び出せるようになりShallow Copyでコピーできるようになります。
Clonableを継承しないクラスでもCloneを呼び出すことはできますが、
”CloneNotSupportedException”が発生しますので正常にコピーできません。
"Java言語で学ぶデザインパターン入門"にならってJavaで実装すると次のような感じです。
Product
Cloneableを継承したProductクラスを用意します。
あとは外部クラスからCloneを実行するためにcreateCloneを用意しておきます。
public interface Product extends Cloneable {
public abstract Product createClone();
}
ProductDefault
Productクラスを実装したProductDefaultクラスを作成します。
createCloneではcloneを呼び出してインスタンスをShallow Copyします。
先程説明したとおりCloneableを継承してないと
"CloneNotSupportedException"が発生するので例外をCatchしておきます。
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クラスの動作を確認してみますと、
違うインスタンスが作成できていますので正常に動作しているようです。
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
abstract class Product : Cloneable {
abstract fun createClone() : Product
}
ProductDefault
class ProductDefault : Product() {
override fun createClone(): ProductDefault {
try {
return clone() as ProductDefault
} catch (e : CloneNotSupportedException) {
throw e
}
}
}
Main
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を使べきシチュエーションが少なくなっているかもしれません。
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を継承するのはクラスでなければならないみたいです。
この内容を深掘りすると長そうなのと、筆者の理解がまだ追いついていないので本記事では省略します。
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