🤖

デザインパターンを学ぶ #11 フライウェイト(Flyweight)

に公開

1. はじめに

今回は Flyweight(フライウェイト)パターン
目的は、大量に生成されるオブジェクトの共通部分を共有し、メモリ使用量を減らすことです。

大量のオブジェクトを一つずつ完全に持つと重すぎるので、
変わらない部分は使い回し(共有)変わる部分だけ個別に持つという発想です。

2. Flyweightとは?

  • オブジェクトを「共有できる内部状態(intrinsic)」と
    個別に与える外部状態(extrinsic)」に分ける
  • 内部状態は 再利用するインスタンスに持たせ、
    外部状態は 呼び出し側から渡す

3. 実装イメージ(PHP)

例:地図上に大量のマーカー(ピン)を表示する場合

  • 色や形 → 共通(共有Flyweight)
  • 座標 → 個別(外部状態として後から渡す)
<?php

// 共有される部分(色やアイコンなど)
class MarkerIcon {
    public function __construct(
        public string $color,
        public string $shape
    ) {}
    public function draw(int $x, int $y): void {
        echo "Draw {$this->color}-{$this->shape} marker at ({$x}, {$y})\n";
    }
}

// Flyweight Factory:既存インスタンスを使い回す
class MarkerIconFactory {
    /** @var MarkerIcon[] */
    private array $cache = [];

    public function get(string $color, string $shape): MarkerIcon {
        $key = $color . '-' . $shape;
        if (!isset($this->cache[$key])) {
            $this->cache[$key] = new MarkerIcon($color, $shape);
        }
        return $this->cache[$key];
    }
}

// --- 利用 ---
$factory = new MarkerIconFactory();

// 1万個のマーカーを配置(色ごとに共有)
$coords = [[10,20], [11,21], [12,22]];
foreach ($coords as [$x, $y]) {
    $icon = $factory->get('red', 'pin'); // 共有オブジェクト
    $icon->draw($x, $y);                  // 外部状態(座標)を後から渡す
}

出力例:

Draw red-pin marker at (10, 20)
Draw red-pin marker at (11, 21)
Draw red-pin marker at (12, 22)

4. 利用シーン

  • 地図やゲームでの 大量オブジェクト表示(タイル、弾丸、兵士、樹木など)
  • フォントや文字オブジェクト(文字の形は共有、座標は個別)
  • IDEの構文ノード(トークンの型情報を共有)

5. メリット

  • メモリ使用量を大幅に削減できる
  • オブジェクト生成コストを抑えられる
  • 同一属性の変更を一括で反映できる(共有部分なので)

6. 注意点

  • 内部状態と外部状態の切り分け設計が必要(どこまで共有するか)
  • 実装が複雑になりやすい → 本当に大量に生成される場合だけ使う

7. 他パターンとの比較

  • Prototype:複製で高速生成、共有ではない
  • Singleton:インスタンス1個固定、Flyweightは同種を複数共有
  • Proxy:アクセス制御や遅延生成が目的、Flyweightは共有による軽量化が目的

8. テスト観点

  • 同じ属性で取得したオブジェクトが同一インスタンスか(===で確認)
  • 共有部分の変更が全箇所に反映されるか
  • メモリ使用量や生成回数が減っている

9. まとめ

Flyweightは、共通部分を共有・個別部分を外部から渡すことで
大量オブジェクトを効率的に扱う構造パターンです。

Discussion