
同じようなオブジェクトを大量に作るときに、便利に使えるのが、Flyweightパターンです。
共通部分を共有してメモリ使用量を減らすことができます。
名前の意味としては、“Flyweight”= 軽量級と言うことで、
オブジェクトを「軽く」してメモリを節約する発想からきているようです。
このデザインパターンをわかりやすく例えると、
1万個の「Tree オブジェクト」を描画するゲームで、毎回 new Tree("oak", "green.png") するととても重い。
そこで、木の「見た目」や「種類」は共通化して、「位置」や「サイズ」などの可変情報だけ別に持たせる。
よく使われる場面
・ゲーム(木・敵・パーティクルなどの大量描画)
・フォントや文字レンダリング(同じ字形を共有)
・Webアプリでのアイコン・ボタンのキャッシュ管理
・シンタックスハイライトなどのトークン共有
サンプルコード
Flyweight(共有される共通データ)
class TreeType {
constructor(name, color, texture) {
this.name = name;
this.color = color;
this.texture = texture;
}
draw(x, y) {
console.log(`🌳 ${this.name} (${this.color}, ${this.texture}) at [${x}, ${y}]`);
}
}
Flyweight Factory(共有インスタンスを管理)
class TreeFactory {
constructor() {
this.treeTypes = {};
}
getTreeType(name, color, texture) {
const key = `${name}_${color}_${texture}`;
if (!this.treeTypes[key]) {
console.log("➡️ 新しいTreeTypeを生成:", key);
this.treeTypes[key] = new TreeType(name, color, texture);
}
return this.treeTypes[key];
}
}
Context(固有情報をもつ)
class Tree {
constructor(x, y, type) {
this.x = x;
this.y = y;
this.type = type;
}
draw() {
this.type.draw(this.x, this.y);
}
}
使用例
const factory = new TreeFactory();
const forest = [];
forest.push(new Tree(10, 20, factory.getTreeType("Oak", "Green", "oak.png")));
forest.push(new Tree(30, 50, factory.getTreeType("Oak", "Green", "oak.png"))); // ← 同じインスタンスを再利用
forest.push(new Tree(40, 10, factory.getTreeType("Pine", "DarkGreen", "pine.png")));
forest.forEach(tree => tree.draw());
ポイント解説
インスタンスの概念です。
参照メモリみたいなイメージで考えるとわかりやすいかもしれません(わかりやすいか?)
TreeType は「共有できる共通情報(名前・テクスチャなど)」を持つオブジェクト。
Tree は「個別情報(位置など)」を持つオブジェクト。
TreeFactory が重複を避けて 同じ TreeType を再利用する。
これにより、大量のオブジェクトでもメモリ効率が良くなる感じです。
Canvasで描画するサンプルコード
sample.html
<canvas id="forest" width="500" height="300" style="border:1px solid #ccc;"></canvas>
<script>
// --- Flyweight(共通データ:木の種類) ---
class TreeType {
constructor(color, size) {
this.color = color;
this.size = size;
}
draw(ctx, x, y) {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x - this.size / 2, y + this.size);
ctx.lineTo(x + this.size / 2, y + this.size);
ctx.closePath();
ctx.fill();
// 幹
ctx.fillStyle = "#8B4513";
ctx.fillRect(x - 2, y + this.size, 4, 8);
}
}
// --- Flyweight Factory(共有インスタンス管理) ---
class TreeFactory {
constructor() {
this.types = {};
}
getTreeType(color, size) {
const key = `${color}_${size}`;
if (!this.types[key]) {
console.log("新しいTreeType生成:", key);
this.types[key] = new TreeType(color, size);
}
return this.types[key];
}
}
// --- Context(固有データ:位置) ---
class Tree {
constructor(x, y, type) {
this.x = x;
this.y = y;
this.type = type;
}
draw(ctx) {
this.type.draw(ctx, this.x, this.y);
}
}
// --- 森を生成 ---
const canvas = document.getElementById("forest");
const ctx = canvas.getContext("2d");
const factory = new TreeFactory();
const forest = [];
const colors = ["green", "darkgreen", "lightgreen"];
for (let i = 0; i < 100; i++) {
const x = Math.random() * canvas.width;
const y = Math.random() * (canvas.height - 50);
const color = colors[Math.floor(Math.random() * colors.length)];
const type = factory.getTreeType(color, 20);
forest.push(new Tree(x, y, type));
}
// --- 描画 ---
forest.forEach(tree => tree.draw(ctx));
console.log("生成された TreeType 数:", Object.keys(factory.types).length);
</script>
メリット
・メモリ使用量を大幅に削減。
・同じ種類のデータを大量に扱う場合に高速化。
デメリット
・実装が少し複雑(Factoryと区別する必要あり)。
・外部状態(位置など)を管理するコストが増える。
・オブジェクトのライフサイクルが分かりづらくなる。
あとがき
ゲームっぽいサンプルをつけたのですが、ユーザーデータの表示をたくさん行う、管理画面などでもFlyweightパターンを使って作ることがイメージできますね。
メモリ容量を抑えることがこのデザインパターンの目的なので、システムのエコ化する処理でリファクタリングする際などにも使えそうですね。
0 件のコメント:
コメントを投稿