gof-composite について
gof-composite は、部分と全体の階層を表す Composite パターンを解説するスキルです。葉と枝でインターフェースを共有する判断や、型安全性と再帰のトレードオフを扱います。
主な機能
- 01葉と枝で共有する Component インターフェース
- 02再帰的な操作の委譲
- 03透過型と安全型 Composite の比較
- 04選択基準とアンチパターン
活用シーン
- 01ファイルとディレクトリのモデル化
- 02入れ子メニューや GUI コンポーネントの構築
- 03階層構造に Composite が必要かの判断
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ yonatankarp/ software-design-skills --skill gof-compositeインストール後、Claude Code で/gof-compositeを入力して読み込まれたか確認します。
詳細
1 行での概要
オブジェクトをツリー構造に組み合わせて全体と部分の階層を表現し、クライアントが個々のオブジェクト(リーフ)とオブジェクトの組み合わせ(ブランチ)を同じように扱えるようにします。
このスキルを使う場面
- ファイルシステム(ファイルとディレクトリ)、メニューとサブメニュー、GUI コンポーネント(ボタンとコンポーネントのパネル)、組織階層など、全体と部分の階層構造がある場合。
- クライアントコードでリーフとブランチのどちらにも同じ操作を呼び出し、どちらを扱っているかを意識せずに済ませたい場合。
- 再帰的な構造を、呼び出し側のコードに再帰を意識させずに扱いたい場合。
このスキルを使わない場面
- 単なる平坦なコレクションの場合。階層がなければ、リストを使ってください。
- リーフとブランチで操作が実際に異なり、それらを無理に単一のインターフェースにまとめることで違いが見えにくくなる場合。
- グループ化の層が 1 つだけの 2 階層構造の場合。Composite を持ち込まずに
List<Item>だけで十分なことがあります。
主な内容
このパターンでは、リーフとコンポジットが共有する共通インターフェース(Component)を導入します。どちらもクライアントが必要とする操作を実装し、コンポジットは子に委譲して実装します。
Component { operation() }
Leaf { operation() /* does the work */ }
Composite { children: List<Component>
operation() { children.forEach { it.operation() } }
add(c); remove(c); }
透明性と安全性のトレードオフ:
- 透過的な Composite —
Componentがadd(child)を宣言します。リーフもコンポジットもこのメソッドを持ちます。クライアントはどちらも同じように扱えますが、Leaf.add(child)は何もしないか、例外をスローします。 - 安全な Composite —
add(child)はCompositeにのみ置かれます。型安全ですが、クライアントは追加する前に型を確認する必要があります。
HFDP の例では透過的な方式が採用されていますが、実際には、統一的な扱いが本当に必要な場合を除き、安全な方式のほうがすっきりすることがよくあります。
判断の目安
- すべての操作がリーフとブランチの両方に意味を持つ場合、Composite を使う価値があります。
- 統一して適用できる操作が 1 つだけで、残りは異なる場合、その抽象化は無理があります。そもそも Composite が必要かどうかを検討してください。
- 非常に深いツリーや幅の広いツリーでは、スタックを使う再帰的な操作でスタックがあふれないように注意してください。必要に応じて Iterator で平坦化します。
アンチパターン
- リーフでしか意味を持たない操作をコンポジットでは何もしないようにする(またはその逆)Composite。統一性が見せかけにすぎません。
- 実際にはリストである「Composite」。再帰も子を持つブランチもありません。単なるコレクションです。
getChildren()で子を直接公開し、呼び出し側が外部からツリーを変更できる Composite。不変条件が損なわれます。
関連項目
gof-iterator— Composite ツリーをたどる自然な方法です。gof-decorator— 構造は同じ(どちらもラップします)ですが、意図は異なります(Decorator は振る舞いを追加し、Composite は全体と部分を表現します)。gof-kotlin-idioms— シールドクラス階層(sealed interface Component { class Leaf; class Branch(val children: List<Component>) })と網羅的なwhenを組み合わせると、Composite を明示的かつ型安全に表現できます。
参考資料
- Head First Design Patterns(第 2 版)、第 9 章(「適切に管理されたコレクション:Iterator パターンと Composite パターン」)— メニューの中にメニューがある例で Composite を説明しています。
コメント
Markdown 対応