Java
初心者
デザインパターン
GoF
prototype


はじめに

GoFのデザインパターンを紹介している『増補改訂版 Java言語で学ぶデザインパターン入門』を読んで、学んだ内容についてまとめます。


Prototypeパターン


Prototypeとは

日本語に訳すと「原型」を意味します。

この原型となるインスタンスを用いて、他のクラスをコピー(複製)し、新しいインスタンスを作成するパターンのことをPrototypeパターンと言います。

通常はインスタンスを生成する際にはnewを用いますが、Prototypeパターンではnewではなく、全てのクラスのスーパークラスであるjava.lang.Objectクラスで定義されているcloneメソッドを用いてインスタンスの作成を行います。


登場人物

Prototypeパターン使用するのは以下のクラス図に登場するクラスです。

image.png


抽象クラス



  • Prototype

    createCloneメソッドを定義するクラスです。

    実装はサブクラスのConcreatePrototypeクラスで行います。


実装クラス


  • ConcreatePrototype

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


  • Client

    createCloneメソッドを使用するクラスです。



具体例

具体例として、以下のクラスをもとに説明します。

image.png


インタフェース



  • Productインタフェース



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クラス


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クラス



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クラス



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クラス



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メソッドでUnderlineSurroundインスタンスの登録を行っています。

生成において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パターンに関して学びました。

以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。

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


参考文献