はじめに
GoFのデザインパターンを紹介している『増補改訂版 Java言語で学ぶデザインパターン入門』を読んで、学んだ内容についてまとめます。
Prototypeパターン
Prototypeとは
日本語に訳すと「原型」を意味します。
この原型となるインスタンスを用いて、他のクラスをコピー(複製)し、新しいインスタンスを作成するパターンのことをPrototypeパターンと言います。
通常はインスタンスを生成する際にはnewを用いますが、Prototypeパターンではnewではなく、全てのクラスのスーパークラスであるjava.lang.Object
クラスで定義されているclone
メソッドを用いてインスタンスの作成を行います。
登場人物
Prototypeパターン使用するのは以下のクラス図に登場するクラスです。
抽象クラス
-
Prototype
createClone
メソッドを定義するクラスです。
実装はサブクラスのConcreatePrototype
クラスで行います。
実装クラス
-
ConcreatePrototype
スーパークラスのPrototype
クラスで定義されたcreateClone
メソッドの実装を行います。 -
Client
createClone
メソッドを使用するクラスです。
具体例
インタフェース
- Productインタフェース
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クラス
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クラス
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クラス
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クラス
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パターンに関して学びました。
以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。
また、他のデザインパターンに関しては以下でまとめていますので、こちらも参考にどうぞ。