【Java】Prototypeパターン【デザインパターン】
目次
Prototypeパターンについて
Prototypeパターンとは
サンプルプログラム
- Prototypeパターンを使って、文字列を枠線で囲んだり、下線を引いて表示する例を取り上げます
- 各クラスやインターフェースの役割は以下のようになっています
名前 | 役割 |
---|---|
Product (Prototype) | 抽象メソッドuse,createCloneを宣言するインターフェース |
Manager (Client) | createCloneを使ってインスタンスを複製するクラス |
ManagerBox (ConcretePrototype) | 文字列を枠線で囲って表示するクラス use,createCloneを実装 |
UnderlinePen (ConcretePrototype) | 文字列に下線を引いて表示するクラス use,createCloneを実装 |
Productインターフェース
java.lang.Cloneable
インターフェースを継承し、cloneメソッドで自動的に複製を行います- Cloneableインターフェースはマーカーインターフェースです
package framework; import java.lang.Cloneable; public interface Product extends Cloneable { public abstract void use(String s); public abstract Product createClone(); }
Managerクラス
- インスタンスの複製を行います
- showcaseフィールドに、インスタンスの名前とインスタンスの対応関係をHashMapで保存します
- registerメソッドでは、製品の名前とProductインターフェースを与えられたら、showcaseに登録します
- 引数のProductインターフェース(proto)は、Productインターフェースを実装したクラスのインスタンスで、use,createCloneメソッドを呼ぶことができます
- ProductインターフェースやManagerクラスにはManagerBoxやUnderlinePenクラス名は使用しません
- つまり、ProductインターフェースやManagerクラスは、独立に修正することができます(疎結合)
- Productインターフェースが、Managerクラスと他のクラスを紐づけています
package framework; import java.util.*; public class Manager { private HashMap showcase = new HashMap(); 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(); } }
MessageBoxクラス
- Productインターフェースを実装します
- createCloneメソッドで呼び出しているcloneメソッドは自分自身の複製を行うメソッドで、インスタンスが持つフィールド値も新しいインスタンスにコピーします
- cloneメソッドは浅いコピー(shallow copy)で、参照のみコピーします
- cloneメソッドでコピーできるのは、
java.lang.Cloneable;
インターフェース実装クラスのみです - インターフェースが実装されていないと、CloneNotSupportedExceptionが投げられるので、try...catch構文で例外処理を実装します
- MessageBoxクラスで実装しているProductインターフェースで、Cloneableインターフェースを実装しているので、例外は投げられません
- cloneメソッドは自分とそのサブクラスからしか呼べないので、別のメソッド(createClone)で包む必要があります
import framework.*; public class MessageBox implements Product { private char decochar; //囲み文字(*など) public MessageBox(char decochar) { this.decochar = decochar; } 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(""); } public Product createClone() { Product p = null; try { p = (Product)clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return p; } }
UnderlinePenクラス
- Productインターフェースを実装します
- 与えられた文字列を引用符で括り、ulcharで与えられた文字で下線を引きます
import framework.*; public class UnderlinePen implements Product { private char ulchar; public UnderlinePen(char ulchar) { this.ulchar = ulchar; } 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(""); } public Product createClone() { Product p = null; try { p = (Product)clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return p; } }
Mainクラスで動作確認
import 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('/'); manager.register("strong message", upen); manager.register("warning box", mbox); manager.register("slash box", sbox); // 生成 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."); } }
Prototypeパターンのメリット
- ManagerクラスはcreateCloneを呼びますが、どのクラスのインスタンスを複製するかは知らなくとも、Productインターフェースを実装するクラスであれば、そのインスタンスを複製できます
- Productインターフェースを実装するクラスのインスタンスを作成し、Managerに登録しておけば好きな時に複製できます
- →こうすることで雛形をいくらでも増やすことができます(
~
で文字列に下線を引いたり、*
や/
で文字列に枠をつける等)- それぞれ別のクラスにしていたら、クラスの数が多くなり管理が大変になります。。
- インスタンスの複製(clone)をframeworkパッケージに閉じ込めています
- →インスタンスを生成するフレームワークを、特定のインスタンスに依存しない様に作ることができます
今日のポイント
- Prototypeパターンは、クラスからインスタンスを生成するのではなく、模型となるインスタンスから別のインスタンスを複製(clone)して生成するパターンです
- PrototypeのインターフェースやClientクラスにはConcretePrototypeクラス名は使用しません(=疎結合である)
- つまり、PrototypeのインターフェースやClientクラスは、切り離して部品として再利用、修正することができます
- インスタンスの複製(clone)をframeworkパッケージに閉じ込めることで、インスタンスを生成するフレームワークを、特定のインスタンスに依存しない様に作ることができます
本日もお疲れ様です😊