mcpField
G
gof-decorator

gof-decorator

github.com/yonatankarp開発143 回インストール23 週間前に更新
リポジトリ

gof-decorator について

gof-decoratorは、オブジェクトを変更せずに振る舞いを拡張する設計パターンを学ぶ開発者向けスキルです。インターフェースを保つラッパーの構成や順序、注意点を解説します。

主な機能

  1. 01実行時の振る舞い拡張
  2. 02インターフェースを保つラッパー
  3. 03組み合わせ可能な単一責務のデコレーター
  4. 04順序の指針と注意点

活用シーン

  1. 01オブジェクトへのログ記録やキャッシュ追加
  2. 02サブクラスを増やさず任意機能を組み合わせる
  3. 03Java I/Oのようなオブジェクトチェーンの拡張

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/yonatankarp/software-design-skills --skill gof-decorator

インストール後、Claude Code で/gof-decoratorを入力して読み込まれたか確認します。

詳細

1 行での概要

オブジェクトを、同じインターフェースを共有して機能を追加する別のオブジェクトで包みます。元のオブジェクトを変更せず、実行時に任意の組み合わせで追加できます。

このスキルを使う場面

  • クラスに直交する拡張が N 個必要な場合(コーヒーにミルク、砂糖、フォーム、シロップなどを任意に組み合わせて加えるなど)。サブクラス化では N! 個のバリエーションが必要になりますが、デコレーターなら小さなラッパーを N 個作るだけです。
  • Java の I/O ストリーム(BufferedReader(InputStreamReader(FileInputStream(...))))は、教科書に載るような現実世界の例です。
  • 既存のオブジェクトのコードに手を加えず、ロギング、キャッシュ、検証、レート制限などの横断的関心事を追加する場合。

このスキルを使わない場面

  • 拡張同士が直交していない場合(ミルクを加える前に砂糖を加える必要があるなど)。デコレーターでは順不同で合成できるため、順序が重要なら隠れた結合が生じています。
  • 拡張が 1 つしかなく、2 つ目も見込まれない場合。サブクラス化やフラグの方がシンプルです。
  • ラッパーが振る舞いだけでなくインターフェースも変更する場合。その場合は gof-adapter を使います。

中核となる内容

構造は 2 つの要素から成ります。

  • 「コア」オブジェクトとすべてのデコレーターが共有する共通インターフェース(または抽象クラス)。
  • 各デコレーターは、同じインターフェースを持つ「ラップされた」インスタンスへの参照を保持し、その呼び出しを転送します。必要に応じて、前後に処理を追加できます。

合成は外側から内側へと読みます。Foam(Milk(Espresso)) は「エスプレッソにミルクを加え、その後フォームを加える」という意味です。クライアントが使うのは最外側のデコレーターです。

「オープン・クローズドの原則」が標準的な動機です。クラスは拡張に対して開かれ、変更に対して閉じられるべきです。デコレーターはサブクラス化ではなく合成によって拡張を実現します。

判断のヒント

  • コアクラス内に if (hasMilk) … if (hasSugar) … if (hasFoam) … と書くことになるなら、デコレーターによって各関心事を独立した型に分けられます。
  • 各デコレーターが追加する関心事は1 つにしてください。デコレーターが 2 つのことをするなら、分割してください。
  • ラップする順序が、外側から内側への実行順序になります。意図的に決め、その順序をテストしてください。

アンチパターン

  • 「デコレーターのごった煮」 — 関心事が重複する小さなデコレーターが多数あり、実際の呼び出しチェーンを把握しにくくなります。
  • 契約を変更するデコレーター(ラップ対象のオブジェクトが返さない場面で null を返すなど) — 呼び出し元をひそかに破壊します。
  • 「デコレーターは余計なものを加えたサブクラスにすぎないから」と、結局は継承を使って振る舞いを追加すること — デコレーターを使う意味がなくなります。

注意点:デコレーターはラップ対象の型ではありません

デコレーターが実装するのは、ラップ対象の具体クラスではなく、そのインターフェースです。そのため、デコレーターはラップ対象の具体型の is / instanceof チェックでは false になります。たとえその型と同じように振る舞うとしても、です。

val loggedRepo: Repository = LoggingRepository(DefaultRepository())
loggedRepo is Repository           // true
loggedRepo is DefaultRepository    // false — even though it wraps one
loggedRepo is LoggingRepository    // true

デコレーターチェーンを「通して」実行時の型チェックを行うと、この問題に直面します。「基になるオブジェクトは X 型か」と尋ねたくなったなら、すでに抽象化が漏れています。チェーンをインターフェース経由で扱うか、構造を見直してください。

関連項目

  • gof-adapter — 振る舞いだけでなくインターフェースを変更するラッパーを使う場合。
  • gof-proxy — 同じ構造ですが、目的は異なります(プロキシはアクセスを制御し、デコレーターは振る舞いを追加します)。
  • gof-composite — デコレーターとコンポジットは構造的に近く、どちらもラップし、再帰的に呼び出しを転送します。
  • gof-kotlin-idioms — Kotlin の by キーワード(クラス委譲)を使うと、デコレーターを 1 行で書けます。

参考資料

  • Head First Design Patterns(第 2 版)、第 3 章(「オブジェクトを装飾する:デコレーターパターン」) — Starbuzz コーヒーの代表的な例と Java I/O の解説。
  • Kotlin Design Patterns and Best Practices(Alexey Soshin)、第 3 章 — デコレーターについて。「デコレーターデザインパターンの注意点」の節では、上記の is チェックに関する注意点の動機が説明されています。

コメント