プロトタイプパターンは、コストの高いオブジェクト生成を回避しつつ同一構造のインスタンスを多数用意したい場合に有効な生成パターンである。このパターンでは、あらかじめ用意した原型(プロトタイプ)を複製することで新しいオブジェクトを得る。以下では、概念の整理とともに、Javaによる具体的な実装例を示す。
パターンの概要
目的: 生成すべきオブジェクトの種類を原型インスタンスによって指定し、その複製によって新たなインスタンスを作成する。
解決する課題: オブジェクトの初期化に多大なリソースが必要な場合や、実行時に生成するクラスが動的に決定される状況で、柔軟かつ高速なインスタンス生成を実現する。
適用が有効な場面:
- データベースアクセスやネットワーク通信など、生成コストの高いオブジェクトを繰り返し利用する必要がある。
- new演算子による直接生成が困難な複雑な初期化手順を避けたい場合。
- 同一の状態セットを持つ複数のバリエーションを簡単に用意したいとき。
実装の核心: 複製をサポートするインタフェース(JavaではCloneableとclone()メソッド)を利用し、原型オブジェクトから複製を作成する。浅いコピーと深いコピーを使い分けることで、オブジェクト間の参照関係を適切に扱う。
実装サンプル
ここでは、文書管理システムを想定し、各種のテンプレート文書をプロトタイプとして登録し、必要に応じて複製する例を取り上げる。
1. 複製可能な抽象クラス
すべての文書テンプレートは、複製のためにCloneableを実装した抽象クラスDocumentTemplateを継承する。
public abstract class DocumentTemplate implements Cloneable {
private String templateId;
protected String category;
public abstract void printContent();
public String getTemplateId() { return templateId; }
public void setTemplateId(String templateId) { this.templateId = templateId; }
public String getCategory() { return category; }
@Override
public DocumentTemplate clone() {
try {
return (DocumentTemplate) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError("Clone not supported", e);
}
}
}
2. 具象テンプレートクラス
異なる文書カテゴリに対応する具象クラスを定義する。各クラスはコンストラクタでカテゴリを設定する。
public class ReportTemplate extends DocumentTemplate {
public ReportTemplate() {
this.category = "Report";
}
@Override
public void printContent() {
System.out.println("レポートテンプレートの内容を出力");
}
}
public class InvoiceTemplate extends DocumentTemplate {
public InvoiceTemplate() {
this.category = "Invoice";
}
@Override
public void printContent() {
System.out.println("請求書テンプレートの内容を出力");
}
}
public class ContractTemplate extends DocumentTemplate {
public ContractTemplate() {
this.category = "Contract";
}
@Override
public void printContent() {
System.out.println("契約書テンプレートの内容を出力");
}
}
3. プロトタイプを管理するキャッシュ
テンプレートのインスタンスをHashMapに格納し、要求に応じてクローンを返すファクトリクラスを用意する。初期化時にあらかじめ代表的な原型をロードしておく。
import java.util.HashMap;
import java.util.Map;
public class TemplateCache {
private Map<String, DocumentTemplate> templateStore = new HashMap<>();
public TemplateCache() {
loadDefaults();
}
private void loadDefaults() {
ReportTemplate report = new ReportTemplate();
report.setTemplateId("rpt-001");
templateStore.put(report.getTemplateId(), report);
InvoiceTemplate invoice = new InvoiceTemplate();
invoice.setTemplateId("inv-001");
templateStore.put(invoice.getTemplateId(), invoice);
ContractTemplate contract = new ContractTemplate();
contract.setTemplateId("ctr-001");
templateStore.put(contract.getTemplateId(), contract);
}
public DocumentTemplate fetchClone(String templateId) {
DocumentTemplate prototype = templateStore.get(templateId);
if (prototype == null) {
throw new IllegalArgumentException("テンプレートが見つかりません: " + templateId);
}
return prototype.clone();
}
}
4. クライアントコード
テンプレートキャッシュから複製を取得し、内容を表示する。
public class PrototypeDemo {
public static void main(String[] args) {
TemplateCache cache = new TemplateCache();
DocumentTemplate doc1 = cache.fetchClone("rpt-001");
System.out.println("取得したテンプレートの種別: " + doc1.getCategory());
doc1.printContent();
DocumentTemplate doc2 = cache.fetchClone("inv-001");
System.out.println("取得したテンプレートの種別: " + doc2.getCategory());
DocumentTemplate doc3 = cache.fetchClone("ctr-001");
System.out.println("取得したテンプレートの種別: " + doc3.getCategory());
}
}
5. 実行結果
取得したテンプレートの種別: Report
レポートテンプレートの内容を出力
取得したテンプレートの種別: Invoice
取得したテンプレートの種別: Contract
この例では、プロトタイプを登録したキャッシュを通じて、元のインスタンスを変更することなく複数の独立したオブジェクトを簡単に得られる。プロトタイプパターンは、オブジェクト生成の負荷を減らし、クラス階層の結合度を低めるのに役立つ。