オブジェクト指向で大切になるInterfaceの考え方やオブジェクトの再利用性を学ぶために「Java言語で学ぶデザインパターン入門」について学び、Javaとついでにkotlinで書いてみることにしました。
今回はPrototypeについて書いてみます。
※また、コメント欄にレビューをいただきました @htsign さんありがとうございます。
レビューを元に修正しました内容を反映させましたので、その点も踏まえて書いていこうと思います。
##Prototypeとは
インスタンスを作成する際はクラスを元にするが、既存のインスタンスをコピーして別のインスタンスとして複製するパターン。
下記のような場合にメリットがあるとのこと。
- 種類が多すぎてクラスにまとめられない場合
- クラスからのインスタンス生成が難しい場合
- フレームワークと生成するインスタンスを分けたい場合
また、サンプルコードでは原型となるインスタンスを元に新しいインスタンスを作成するパターンを提供しています。
##Productインターフェース
Cloneableを継承しているインターフェースでProductクラスを継承しているサブクラスのインスタンスがコピー対象のクラスとなります。
KotlinではCloneableがProductインターフェースで実装されている場合はjava.lang.NoClassDefFoundError
になります。どうやらインターフェースでは実装できないらしく、Cloneableを継承するのはクラスでなければならないとの事です。
また、Cloneableを継承した抽象クラスのサブクラスの場合は実装できることを確認しました。
interface Product extends Cloneable {
public abstract void use(String s);
public abstract Product createClone();
}
interface Product {
fun use(s: String)
fun createClone(): Product
}
Product抽象クラスの場合
abstract class Product(): Cloneable {
abstract fun use(s: String)
abstract fun createClone(): Product
}
##Managerクラス
createCloneを使ってインスタンスを複製するクラスで、登録しておけば好きなタイミングで複製が可能なクラスになってます。
HashMapはshowcase.get()
でもshowcase[]
でも取得可能。
kotlinだとval a: String = null
ではコンパイルエラーとなり、デフォルトでNullを許容していないので?指定をするとNullが扱えます。また、?指定したpがNullならcreateCloneは実行されずNullが返ります。
参考:
【Kotlin】【Java】Kotlin Javaの比較メモ
【Null安全】Kotlin Java比較メモ 正しいNull安全の使い方
『増補改訂版Java言語で学ぶデザインパターン入門』をKotlinでやってみる(Prototype編)
@htsign さんレビューをいただきありがとうございます。
元々Product
のサブクラスでtry文のブロックの外にProduct型の変数pを宣言したくて
Null
を代入し、?
指定にしたのですが、lateinit
を使用して初期化を遅延する処理に修正し、Nullを扱わないように修正しました。
なのでProduct
インターフェースのcreateClone()
も戻り値をProduct?
からProduct
に戻してあります。
class Manager {
private HashMap showcase = new HashMap();
public void register(String name, Product proto) {
showcase.put(name, proto);
}
public Product create(String protoname) {
Product p = (Product)showcase.get(protoname);
return p.createClone();
}
}
class Manager {
private var showcase: MutableMap<String, Product> = mutableMapOf()
fun register(name: String, proto: Product){
showcase.put(name, proto)
}
fun create(protoname: String): Product?{
val p = showcase[protoname] as Product
return p?.createClone()
}
}
class Manager {
private var showcase: MutableMap<String, Product> = mutableMapOf()
fun register(name: String, proto: Product){
showcase.put(name, proto)
}
fun create(protoname: String): Product{
val p = showcase[protoname] as Product
return p.createClone()
}
}
##MessageBoxクラス
文字列を枠線で囲って表示するクラスです。
先ほど言及しましたCloneableはProductのサブクラスにて実装する必要があります。
また、cloneメソッドは自分のクラス(およびサブクラス)からしか呼び出せないので、他のクラス(Manager)からの要請で複製を行う場合は、createClone()
のような別メソッドでcloneをくるんで呼び出す必要があるとのことです。
class MessageBox implements Product {
private char decochar;
public MessageBox(char decochar) {
this.decochar = decochar;
}
public void use(String s) {
int length = s.getBytes().length;
for (int i = 0; i < length + 4; i++) {
System.out.print(decochar);
}
System.out.println("");
System.out.println(String.format("%s %s %s", decochar, s, decochar));
for (int i = 0; i < length + 4; i++) {
System.out.print(decochar);
}
System.out.println("");
}
public Product createClone(){
Product p = null;
try {
p = (Product)clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}
class MessageBox(private val d: Char): Product, Cloneable{
override fun use(s: String){
val length = s.toByteArray().size
for (i: Int in 1..length + 4) print(d)
println("")
println("%s %s %s".format(d, s, d))
for (i: Int in 1..length + 4) print(d)
println("")
}
override fun createClone(): Product?{
var p: Product? = null
try {
p = clone() as Product
}catch (e:CloneNotSupportedException){
e.printStackTrace()
}
return p
}
}
class MessageBox(private val d: Char): Product, Cloneable{
override fun use(s: String){
val length = s.toByteArray().size
for (i: Int in 1..length + 4) print(d)
println("")
println("%s %s %s".format(d, s, d))
for (i: Int in 1..length + 4) print(d)
println("")
}
override fun createClone(): Product{
lateinit var p: Product
try {
p = clone() as Product
}catch (e:CloneNotSupportedException){
e.printStackTrace()
}
return p
}
}
##UnderlinePenクラス
文字列に下線を引いて表示するクラスです。
class UnderlinePen implements Product {
private char ulchar;
public UnderlinePen(char ulchar) {
this.ulchar = ulchar;
}
public void use(String s) {
int length = s.getBytes().length;
System.out.println(String.format("\"%s\"", s));
System.out.print(" ");
for (int i = 0; i < length; i++) {
System.out.print(ulchar);
}
System.out.println("");
}
public Product createClone() {
Product p = null;
try {
p = (Product)clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}
class UnderLinePen(ulchar: Char): Product, Cloneable{
private val u = ulchar
override fun use(s: String) {
val length = s.toByteArray().size
println("\"%s\"".format(s))
print(" ")
for (i: Int in 1..length) print(u)
println("")
}
override fun createClone(): Product? {
var p: Product? = null
try {
p = clone() as Product
}catch (e: CloneNotSupportedException){
e.printStackTrace()
}
return p
}
}
class UnderLinePen(ulchar: Char): Product, Cloneable{
private val u = ulchar
override fun use(s: String) {
val length = s.toByteArray().size
println("\"%s\"".format(s))
print(" ")
for (i: Int in 1..length) print(u)
println("")
}
override fun createClone(): Product {
lateinit var p: Product
try {
p = clone() as Product
}catch (e: CloneNotSupportedException){
e.printStackTrace()
}
return p
}
}
##Mainクラス
public class PrototypeSample {
public static void main(String[] args) {
Manager manager = new Manager();
UnderlinePen upen = new UnderlinePen('~');
MessageBox mbox = new MessageBox('*');
MessageBox sbox = new MessageBox('/');
manager.register("strong message", upen);
manager.register("warning box", mbox);
manager.register("slash box", sbox);
Product p1 = manager.create("strong message");
Product p2 = manager.create("warning box");
Product p3 = manager.create("slash box");
p1.use("Hello, World.");
p2.use("Hello, World.");
p3.use("Hello, World.");
}
}
fun main(args: Array<String>) {
val manager = Manager()
val upen = UnderLinePen('~')
val mbox = MessageBox('*')
val sbox = MessageBox('/')
manager.register("strong message", upen)
manager.register("warning box", mbox)
manager.register("slash box", sbox)
val p1 = manager.create("strong message")
val p2 = manager.create("warning box")
val p3 = manager.create("slash box")
p1?.use("Hellow, World.")
p2?.use("Hellow, World.")
p3?.use("Hellow, World.")
}
fun main(args: Array<String>) {
val manager = Manager()
val upen = UnderLinePen('~')
val mbox = MessageBox('*')
val sbox = MessageBox('/')
manager.register("strong message", upen)
manager.register("warning box", mbox)
manager.register("slash box", sbox)
val p1 = manager.create("strong message")
val p2 = manager.create("warning box")
val p3 = manager.create("slash box")
p1.use("Hellow, World.")
p2.use("Hellow, World.")
p3.use("Hellow, World.")
}
"Hello, World."
~~~~~~~~~~~~~
*****************
* Hello, World. *
*****************
/////////////////
/ Hello, World. /
/////////////////
##所感
- cloneについてそもそもなぜ必要なのかを併せて調べてみたのですが(参考:Java の clone() メソッドについて)継承されたサブクラスなどでインスタンスを作成する際に意図していない動作をするので、よりそのままインスタンスをコピーするようにcloneメソッドは設計されたことを学んだ。
- Null安全やHashMapの初期化、
String#getBytes
はtoByteArray()
で代替することを学んだ - また、cloneableインターフェースがkotlinとjavaで仕様が違い、kotlinだとインターフェースに実装できないことを学んだ。
- cloneは
shallow copy
(フィールド先の参照先の値ではなく参照値がコピーされる)なので、インスタンスのフィールドを書き換えた場合、意図せずclone元のフィールドまで書き換える恐れがあるのでdeep copy
(フィールド先の参照先の値までコピー)する際はオーバーライドして処理を足す必要があることを学んだ。 - また、Clonenableインターフェースはメソッドなどの宣言がされてず、あくまで印のようなインターフェースのことを
marker interface
と呼ぶことを学んだ。
##参考
下記を参考にさせて頂き、大変読みやすく、理解しやすかったです。
JavaのString#getBytesはKotlinでどう書く?
Kotlinでデザインパターン Prototype編
06设计模式-kotlin-复制Prototype
【Kotlin】【Java】Kotlin Javaの比較メモ
【Null安全】Kotlin Java比較メモ 正しいNull安全の使い方
『増補改訂版Java言語で学ぶデザインパターン入門』をKotlinでやってみる(Prototype編)
また、clone()
については下記ブログが詳細に説明してくださっていて大変勉強になりました。
Java の clone() メソッドについて