TanukiEngineerの1歩ずつ進もうよ!

まだまだ駆け出しエンジニアの学習メモ

デザインパターン:Prototype(プロトタイプ)パターン

Prototype パターンとは

クラスからインスタンスを生成するのではなく、インスタンスから別のインスタンスを作り出すパターンをPrototype(プロトタイプ)パターンと呼びます。

  • prototypeという英単語は、「原型」や「模範」という意味です。
  • prototypeのインスタンスを元に新しいインスタンスを作ります。
  • Java言語では、複製を作る操作を「clone(クローン)」と呼びます。
  • クラスのインスタンスを作成するとき、Javaでは、newというキーワードを使って、クラス名を指定することでインスタンスを生成します。
  • 例えば、Somethingクラスのインスタンスを作成する場合は、次のような式を書きます。
new something()

Prototypeパターンのクラス図

f:id:TanukiEngineer:20210207150133p:plain

サンプルプログラム

以下、Prototypeパターンを使ったサンプルプログラムです。

クラスとインターフェース一覧
パッケージ 名前 説明
framework Product 抽象メソッドuseとcreateCloneが
宣言されているインターフェース
framework Manager createCloneを使ってインスタンス
複製するクラス
無名 ConcreteProduct ・createCloneを実装
・MessageBoxクラス等のスーパークラス
無名 MessageBox ・文字列を枠線で囲って表示するクラス
・useとcreateCloneを実装
無名 UnderlinePen ・文字列に下線を引いて表示するクラス
・useとcreateCloneを実装
無名 ReplayQuote ・文字列の前に引用符を表示するクラス
・useとcreateCloneを実装
無名 Main 動作テスト用のクラス

サンプルプログラムのクラス図

f:id:TanukiEngineer:20210207221726p:plain

Productインタフェース

  • java.lang.Cloneableインタフェースを継承しています。
  • このインタフェースを実装しているクラスは、cloneメソッドを使用して自動的に複製ができるようになります。
  • useメソッドは、名前のとおり「使う」ためのメソッドです。何をどのように「使う」かはサブクラスの実装に任されます。
  • createCloneメソッドは、インスタンスの複製を行うためのものです。

Product.java

package SampleDesignPattern.framework;

public interface Product extends Cloneable {
    public abstract void use(String s);
    public abstract Product createClone();
}

Managerクラス

  • Managerクラスは、Productインターフェースを利用してインスタンスの複製を行うクラスです。
  • showcaseフィールドは、java.util.HashMapでインスタンスの「名前」と「インスタンス」の対応関係を表現したものです。
  • registerメソッドは、引数で渡された製品の名前とProductインターフェースの1組を登録します。
  • Managerクラスには、Productインターフェースを実装した具体的なクラス名は出てきません。そのため、具体的なクラスとは独立に修正できることを意味しています。ソース中にクラスの名前を書いてしまうと、そのクラスと密接な関係ができてしまします。
  • オブジェクト指向プログラミングの目標の1つである「部品としての再利用」をするため、ソースファイル(.java)がなくても、クラスファイル(.class)があれば再利用できる、ようにしています。

Manager.java

package SampleDesignPattern.framework;
import java.util.HashMap;

public class Manager {
    private HashMap<String, Product> showcase = new HashMap<String, Product>();
    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();
    }
}

ConcreteProduct

  • サブクラスで実装するcreateCloneメソッドは共通の処理であるため、Productインターフェースを実装し、ここでcreateCloneを実装します。
  • createCloneメソッドは、cloneメソッドを使用し、自分自身の複製を行うメソッドです。
  • Java言語のcloneメソッドは、自分のクラス(およびサブクラス)からしか呼び出すことができないため、creageCloneメソッドを作成して、その中でcloneメソッドを使うようにしています。
  • useメソッドは各サブクラスで処理内容が異なるため、抽象メソッドのままとします。

ConcreteProduct.java

package SampleDesignPattern.PrototypePattern;

import SampleDesignPattern.framework.Product;

public abstract class ConcreteProduct implements Product {
    public abstract void use(String s);
    @Override
    public Product createClone() {
        Product p = null;
        try {
            p = (Product)clone();
        }
        catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }
}

MessageBoxクラス

  • 具体的なサブクラス。ConcreteProductクラスを継承しています。
  • useメソッドは、引数で渡された文字列に対して、decocharフィールドの文字で囲みます。

MessageBox.java

package SampleDesignPattern.PrototypePattern;

public class MessageBox extends ConcreteProduct {
    private char decochar;
    public MessageBox(char decochar) {
        this.decochar = decochar;
    }

    @Override
    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(decochar + " " + s + " " + decochar);
        for (int i = 0; i < length + 4; i++) {
            System.out.print(decochar);
        }
        System.out.println("");
    }
}

UnderlinePenクラス

  • 具体的なサブクラス。ConcreteProductクラスを継承しています。
  • useメソッドは、引数で渡された文字列に対して、二重引用符""でくくり、文字列の下にulcharフィールドの文字で下線を引きます。

UnderlinePen.java

package SampleDesignPattern.PrototypePattern;

public class UnderlinePen extends ConcreteProduct {
    private char ulchar;
    public UnderlinePen(char ulchar) {
        this.ulchar = ulchar;
    }

    @Override
    public void use(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("");
    }
}

ReplayQuoteクラス

  • 具体的なサブクラス。ConcreteProductクラスを継承しています。
  • useメソッドは、引数で渡された文字列の先頭にquotecharフィールドの文字を付与します。

ReplayQuote.java

package SampleDesignPattern.PrototypePattern;

public class ReplayQuote extends ConcreteProduct {
    private char quotechar;

    public ReplayQuote(char quotechar) {
        this.quotechar = quotechar;
    }

    @Override
    public void use(String s) {
        System.out.println(quotechar + " " + s);
    }
}

Mainクラス

名前 クラス インスタンスの内容
"strong message" UnderlinePen ulchar:'~'
"warning box" MessageBox decochar:'*'
"slash box" MessageBox decochar:'/'
"greater than quote" ReplayQuote quotechar:'>'

Main.java

package SampleDesignPattern.ReplayQuotePrototypePattern;
import SampleDesignPattern.framework.*;

public class Main {
    public static void main(String[] args) {
        // 準備
        Manager manager = new Manager();
        UnderlinePen upen = new UnderlinePen('~');
        MessageBox mBox = new MessageBox('*');
        MessageBox sBox = new MessageBox('/');
        ReplayQuote gQuotes = new ('>');
        manager.register("strong message", upen);
        manager.register("warning box", mBox);
        manager.register("slash box", sBox);
        manager.register("greater than quote", gQuotes);

        // 生成
        Product p1 = manager.create("strong message");
        p1.use("Hello, World.");
        Product p2 = manager.create("warning box");
        p2.use("Hello, world.");
        Product p3 = manager.create("slash box");
        p3.use("Hello, world.");
        Product p4 = manager.create("greater than quote");
        p4.use("Hello, world.");
    }
}

【実行結果】

"Hello, World."
~~~~~~~~~~~~~
*****************
* Hello, world. *
*****************
/////////////////
/ Hello, world. /
/////////////////
> Hello, world.

参考:詳しくは、以下の書籍を参照してください
  • 「補講:cloneメソッドとjava.lang.Cloneableインターフェース」も参考になります
  • 練習問題もあります

増補改訂版Java言語で学ぶデザインパターン入門 | 結城 浩 |本 | 通販 | Amazon