ストーリー
Prototype パターンとは
目的
既存のオブジェクト(プロトタイプ)をコピー(クローン)して新しいオブジェクトを生成する。コンストラクタを介さずに、実行時の状態をそのまま複製できる。
どんなときに使うか
| 状況 | 説明 |
|---|---|
| 生成コストが高い | DB読み込みやAPI呼び出しが必要なオブジェクト |
| 設定が多い | 多数のプロパティが設定済みのテンプレート |
| 実行時に型が決まる | コンパイル時にどのクラスかわからない |
| 状態の保存・復元 | スナップショットを取りたい |
基本実装
// Clone可能なオブジェクトのインターフェース
interface Cloneable<T> {
clone(): T;
}
// 図形の基底クラス
abstract class Shape implements Cloneable<Shape> {
constructor(
public x: number,
public y: number,
public color: string
) {}
abstract clone(): Shape;
abstract draw(): string;
}
// 具体クラス:円
class Circle extends Shape {
constructor(
x: number,
y: number,
color: string,
public radius: number
) {
super(x, y, color);
}
clone(): Circle {
return new Circle(this.x, this.y, this.color, this.radius);
}
draw(): string {
return `Circle(${this.x}, ${this.y}, r=${this.radius}, ${this.color})`;
}
}
// 具体クラス:長方形
class Rectangle extends Shape {
constructor(
x: number,
y: number,
color: string,
public width: number,
public height: number
) {
super(x, y, color);
}
clone(): Rectangle {
return new Rectangle(this.x, this.y, this.color, this.width, this.height);
}
draw(): string {
return `Rect(${this.x}, ${this.y}, ${this.width}x${this.height}, ${this.color})`;
}
}
// 使い方
const original = new Circle(10, 20, 'red', 50);
const copy = original.clone();
copy.x = 100; // コピーを変更しても元に影響しない
copy.color = 'blue';
console.log(original.draw()); // Circle(10, 20, r=50, red)
console.log(copy.draw()); // Circle(100, 20, r=50, blue)
深いコピー(Deep Clone)
ネストされたオブジェクトを持つ場合、浅いコピーでは参照が共有されてしまいます。
// 浅いコピーの問題
class Document implements Cloneable<Document> {
constructor(
public title: string,
public sections: Section[]
) {}
// 浅いコピー -- sections配列の参照が共有される!
shallowClone(): Document {
return new Document(this.title, this.sections);
}
// 深いコピー -- sections も新しい配列・オブジェクトとしてコピー
clone(): Document {
const clonedSections = this.sections.map(section => section.clone());
return new Document(this.title, clonedSections);
}
}
class Section implements Cloneable<Section> {
constructor(
public heading: string,
public content: string
) {}
clone(): Section {
return new Section(this.heading, this.content);
}
}
// 深いコピーの確認
const doc = new Document('Report', [
new Section('Introduction', 'Hello'),
new Section('Body', 'Content'),
]);
const copy = doc.clone();
copy.sections[0].heading = 'Overview'; // コピーだけ変更
console.log(doc.sections[0].heading); // 'Introduction'(元は変わらない)
console.log(copy.sections[0].heading); // 'Overview'
プロトタイプレジストリ
よく使うプロトタイプをレジストリに登録しておき、名前で取得する方法です。
class ShapeRegistry {
private prototypes: Map<string, Shape> = new Map();
register(name: string, shape: Shape): void {
this.prototypes.set(name, shape);
}
get(name: string): Shape {
const prototype = this.prototypes.get(name);
if (!prototype) throw new Error(`Unknown shape: ${name}`);
return prototype.clone();
}
}
// レジストリにテンプレートを登録
const registry = new ShapeRegistry();
registry.register('small-red-circle', new Circle(0, 0, 'red', 10));
registry.register('large-blue-rect', new Rectangle(0, 0, 'blue', 200, 100));
registry.register('icon-circle', new Circle(0, 0, 'gray', 16));
// 名前で取得してカスタマイズ
const shape1 = registry.get('small-red-circle');
shape1.x = 50;
shape1.y = 100;
const shape2 = registry.get('large-blue-rect');
shape2.x = 200;
TypeScript / JavaScript での活用
TypeScript では structuredClone やスプレッド構文でも簡易的なクローンが可能です。
// スプレッド構文(浅いコピー)
const config = { host: 'localhost', port: 3000, options: { debug: true } };
const copy1 = { ...config };
// structuredClone(深いコピー)-- 関数やクラスインスタンスは非対応
const copy2 = structuredClone(config);
// Prototypeパターンが必要な場面
// - クラスのメソッドも含めてコピーしたい
// - clone時にカスタムロジックが必要
// - ポリモーフィズムを活かしたい(Shape の具体型を知らずにクローン)
高橋アーキテクトのアドバイス:
「TypeScript では、単純なデータオブジェクトなら
structuredCloneで十分なことも多い。Prototype パターンが真価を発揮するのは、クラスの振る舞いも含めて複製したい場合や、ポリモーフィックにクローンしたい場合だ」
まとめ
| ポイント | 内容 |
|---|---|
| Prototype の目的 | 既存オブジェクトをクローンして新しいオブジェクトを生成 |
| 深いコピー | ネストされたオブジェクトも独立してコピー |
| レジストリ | テンプレートを登録して名前でクローンを取得 |
| TypeScript活用 | 単純なケースでは structuredClone も検討 |
チェックリスト
- Prototype パターンの目的と使い所を理解した
- 浅いコピーと深いコピーの違いを説明できる
- プロトタイプレジストリを実装できる
次のステップへ
次は演習です。ここまで学んだ生成パターン(Factory Method、Abstract Factory、Builder、Singleton、Prototype)を実際に実装してみましょう。
推定読了時間: 25分