Help us understand the problem. What is going on with this article?

JavaでPrototypeパターン

More than 1 year has passed since last update.

はじめに

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パターンに関して学びました。
以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。

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

参考文献

mk777
SIer→Webに転職したエンジニア。学んだことなどをまとめていきます。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away