Prototypeパターン


 ★★☆

Prototypeパターン

newではなく、既存のインスタンスをコピーする


prototype という単語は、日本語で「試作品」「原型」といった意味を持ちます。Prototype パターンは、あらかじめ用意しておいた「原型」からインスタンスを生成するようにするためのパターンです。

例えば、「直線を描く」機能しか持たない図形エディターを想像してみましょう。この図形エディターで星型を書きたいときには、直線を組み合わせることで、星の形を作成していくことになります。では、星型がいくつも欲しいときはどうすればよいでしょう?直線を組み合わせて星型を描くという作業を何度も繰り返す必要が出てきます。こんなとき、最初に作成した星型(直線の集まり)を「プロトタイプ」として登録しておき、これをコピーすることで星型が作成できれば、作業がとても楽になります。

Prototype パターンは、このように「プロトタイプからインスタンスを生成する」ことができるようにするためのパターンです。



Prototypeパターンを実現するには、クラスの中に自分のクローンを返すメソッド(clone)を用意しておきます。クローンとは、フィールドの値が等しいインスタンスのことです。

クローンを作るということは、オブジェクト指向プログラミングにおいてとても重要なアイディアです。フィールドの数が多いクラスの場合、すべてを引数として設定したコンストラクタを使ってインスタンスを生成したり、インスタンスを生成した後メソッドを呼んでたくさんのフィールドにいちいち値を設定していくのでは面倒です。すでに生成されているインスタンスのクローンを作る方が効率的です。クラスを作る人が思いやりを込めてcloneメソッドを用意しておいてあげれば、クラスを使う人は楽ができます。


ただし、cloneメソッドによって行われるのは、フィールドの内容をそのままコピーするという動作です。言い換えればフィールドの先にあるインスタンスの中身までは考慮しないということです。例えば、フィールドの先に配列があったとします。cloneメソッドでコピーされるのは、配列への参照のみで、配列の要素1つひとつがコピーされるわけではありません。

また、cloneは、コピーを行うだけであり、コンストラクタを呼ぶわけではありません。インスタンス生成時に何か特殊な初期処理を必要とする場合は、別途記述する必要があります。



例題

文字列を修飾して表示する MessageBox クラスを作りなさい。このクラスは、枠の文字や横幅、文字の不足する部分に埋める文字を設定した後、メッセージを表示します。


実行結果
************************ * Hello, World........ * ************************ ************************ * Ciao, Mondo......... * ************************










Prototypeパターンを使用しない例

MessageBox.java
public class MessageBox { private char decoChar = ' '; private int width = 20; private char padding = ' '; private String message = ""; public MessageBox() {} public void setChar(char c) { decoChar = c; } public void setWidth(int w) { width = w; } public void setPadding(char p) { padding = p; } public void setMessage(String m) { message = m; } public void print() { int strLength = message.getBytes().length; if (strLength > width) { message = message.substring(0, width); } else { int pl = width - strLength; char[] p = new char[pl]; for (int i = 0; i < pl; i++) p[i] = padding; message = message.concat(new String(p)); } for (int i = 0; i < width + 4; i++) { System.out.print(decoChar); } System.out.println(""); System.out.println(decoChar + " " + message + " " + decoChar); for (int i = 0; i < width + 4; i++) { System.out.print(decoChar); } System.out.println(""); } }
Main.java
public class Main { public static void main(String[] args) { MessageBox mbx1 = new MessageBox(); mbx1.setChar('*'); mbx1.setWidth(20); mbx1.setPadding('.'); mbx1.setMessage("Hello, World"); MessageBox mbx2 = new MessageBox(); mbx2.setChar('*'); mbx2.setWidth(20); mbx2.setPadding('.'); mbx2.setMessage("Ciao, Mondo"); mbx1.print(); mbx2.print(); } }

Prototypeパターンを使用した例

MessageBox.java
public class MessageBox implements Cloneable { private char decoChar = ' '; private int width = 20; private char padding = ' '; private String message = ""; public MessageBox() {} public void setChar(char c) { decoChar = c; } public void setWidth(int w) { width = w; } public void setPadding(char p) { padding = p; } public void setMessage(String m) { message = m; } public void print() { int strLength = message.getBytes().length; if (strLength > width) { message = message.substring(0, width); } else { int pl = width - strLength; char[] p = new char[pl]; for (int i = 0; i < pl; i++) p[i] = padding; message = message.concat(new String(p)); } for (int i = 0; i < width + 4; i++) { System.out.print(decoChar); } System.out.println(""); System.out.println(decoChar + " " + message + " " + decoChar); for (int i = 0; i < width + 4; i++) { System.out.print(decoChar); } System.out.println(""); } public MessageBox createClone() { MessageBox p = null; try { p = (MessageBox) clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return p; } }
Main.java
public class Main { public static void main(String[] args) { MessageBox mbx1 = new MessageBox(); mbx1.setChar('*'); mbx1.setWidth(20); mbx1.setPadding('.'); mbx1.setMessage("Hello, World"); MessageBox mbx2 = mbx1.createClone(); mbx2.setMessage("Ciao, Mondo"); mbx1.print(); mbx2.print(); } }

MessageBox のインスタンスを2つ作りました。これら2つのインスタンスは同じ設定なのですが、それぞれ別のインスタンスなので、生成された後、個別に枠の文字や横幅、文字の不足する部分に埋める文字を指定しています。この例では、3つだけでしたが、これが何十個もあったら、そして、少しずつ違う設定だったら...

非常に大変であるということが想像できると思います。

MessageBox クラスは、java.lang.Cloneable を継承(インタフェースなのでインプリメント)しています。

一つ目のインスタンスでは、MessageBox クラスのインスタンスを生成し、メソッドを呼んでフィールドに値を設定しています。しかし、2つ目のインスタンスには、一つ目のインスタンスのコピーを設定しているだけです。これだけです。設定しなければならない項目が何十個あろうが、 createClone() の結果を代入してあげればよいだけなのです。

設定する内容が少しずつ違っていても、コピーをした後、違うところだけ変えてあげればよいのです。

なお、createClone() から呼んでいる clone() は、java.lang.Object.clone() です。