#08 / 23
S構造DIFF
コンポジット
Composite
ファイルもフォルダも、まとめて『中身のサイズが分かるもの』として扱う。個と集合の境界を消す。
Intent · 目的
オブジェクトを木構造に組み立て、葉(個別)とコンテナ(合成)を区別せず同じインタフェースで扱えるようにする。再帰的な構造を、利用側の `if フォルダなら…` を撲滅して、自然に書けるようにするパターン。
Motivation · 動機
「フォルダのサイズを表示してください」── ナイーブに書くと、ファイルなら `size`、フォルダなら子要素を全部足す、と分岐したくなる。さらに『ショートカット』も出てきたら if が増える。Composite は『葉も合成も、`size()` を呼べば正しい数字を返す』という1つの約束で統一する。フォルダは中の子に再帰的に聞くだけで仕事完了。組織図、UI部品(パネルの中にボタンの中に…)、HTML DOM ── 木構造のあるところに Composite あり。
適用場面
- 1対象が再帰的な木構造で表現される(ファイルシステム、UIツリー、組織図、AST)。
- 2個別と集合を区別せず一様に操作したい場合(『全部の合計』『全部に表示』など)。
- 3操作を構造全体に再帰的に適用したい場合。
概念図
サンプルコード
interface FsNode {
size(): number;
name: string;
}
class FileNode implements FsNode {
constructor(public name: string, private bytes: number) {}
size(){ return this.bytes; }
}
class Directory implements FsNode {
private children: FsNode[] = [];
constructor(public name: string) {}
add(node: FsNode){ this.children.push(node); return this; }
size(){ return this.children.reduce((acc, c) => acc + c.size(), 0); }
}
const root = new Directory("/")
.add(new FileNode("a.txt", 100))
.add(new Directory("sub").add(new FileNode("b.txt", 50)));
console.log(root.size()); // 150Pros · メリット
- +クライアントが葉と合成を区別しなくて済む。`if フォルダなら` の地獄から解放。
- +再帰構造を自然に表現でき、操作も再帰呼出し1行で書ける。
- +新しい部品を追加しても、利用側コードは無変更。
Cons · デメリット
- −共通I/Fに `add/remove` を含めると、葉でも呼べてしまい型安全が緩くなる。
- −全要素を共通I/Fに揃えるため、I/Fが太りやすい(『全員に必要じゃないメソッド』が混ざる)。
- −深い木の操作はパフォーマンスとデバッグの両方で気を遣う。
関連パターン
⚔ ready to test
理解度を確かめる時間だ
3 問のクイズで、 このパターンを身体に染み込ませよう。