デザインパターン:Prototype(プロトタイプ)パターン
Prototype パターンとは
クラスからインスタンスを生成するのではなく、インスタンスから別のインスタンスを作り出すパターンをPrototype(プロトタイプ)パターンと呼びます。
- prototypeという英単語は、「原型」や「模範」という意味です。
- prototypeのインスタンスを元に新しいインスタンスを作ります。
- Java言語では、複製を作る操作を「clone(クローン)」と呼びます。
- クラスのインスタンスを作成するとき、Javaでは、newというキーワードを使って、クラス名を指定することでインスタンスを生成します。
- 例えば、Somethingクラスのインスタンスを作成する場合は、次のような式を書きます。
new something()
- しかし、次のような場合、クラス名を指定せずにインスタンスを生成したくなる場合があります。
- 種類が多すぎてクラスにまとめられない場合
扱いたいオブジェクトの種類が多すぎて、1つ1つを別のクラスにすると、ソースファイルを多数作成することになり、ソースプログラムの管理がしにくくなる場合です。 - クラスからのインスタンス生成が難しい場合
ユーザーが操作して作った図形のインスタンスと同じものを再度、作りたいといった場合、 作ったインスタンスを保存しておき、作りたいときにそのインスタンスをコピーします。 - フレームワークと生成するインスタンスを分けたい場合
インスタンスを生成するときの】フレームワークを、特定のクラスに依存しないように作りたい場合、 前もって「ひな形」となるインスタンスを登録しておき、その登録したインスタンスをコピーすることでインスタンスを生成します。
- 種類が多すぎてクラスにまとめられない場合
Prototypeパターンのクラス図
- Prototype(プロトタイプ:原型)の役
インスタンスをコピー(複製)して新しいインスタンスを作るためのメソッドを定めます。 - ConcretePrototype(コンクリート・プロトタイプ:具体的な原型)の役
インスタンスをコピーして新しいインスタンスを作るメソッドを実装します。 - Client(クライアント:利用者)の役
インスタンスをコピーするメソッドを利用して、新しいインスタンスを作ります。
サンプルプログラム
以下、Prototypeパターンを使ったサンプルプログラムです。
クラスとインターフェース一覧
パッケージ | 名前 | 説明 |
---|---|---|
framework | Product | 抽象メソッドuseとcreateCloneが 宣言されているインターフェース |
framework | Manager | createCloneを使ってインスタンスを 複製するクラス |
無名 | ConcreteProduct | ・createCloneを実装 ・MessageBoxクラス等のスーパークラス |
無名 | MessageBox | ・文字列を枠線で囲って表示するクラス ・useとcreateCloneを実装 |
無名 | UnderlinePen | ・文字列に下線を引いて表示するクラス ・useとcreateCloneを実装 |
無名 | ReplayQuote | ・文字列の前に引用符を表示するクラス ・useとcreateCloneを実装 |
無名 | Main | 動作テスト用のクラス |
サンプルプログラムのクラス図
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クラス
- Managerのインスタンスを作ります。
- Managerのインスタンスに対して、UnderlinePenのインスタンス、MessageBoxのインスタンス、ReplayQuoteのインスタンスを名前付きで登録します。
名前 | クラス | インスタンスの内容 |
---|---|---|
"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インターフェース」も参考になります
- 練習問題もあります