継承よりコンポジションの適用
クイックスタート
- 変化軸を特定する: データのバリアント、アルゴリズム、クロスカッティングな振る舞い、またはフレームワークの制約。
- デフォルトはコンポジション: 継承が、代替可能性と安定性によって明確に正当化される場合を除き、デフォルトでコンポジションを選択する。
- バリアント集合が閉じており、データを伴う場合: オープンな継承よりも、代数的データ型(ADT)/ sealed型(sealed type)を優先する。
- 具体的な移行計画を作成する: テスト、ロールアウト順序、レビューのチェックポイントを含めること。
リファレンスルーティング(段階的開示)
現在のタスクに必要なファイルのみを参照してください:
- 基礎とメカニズムの選択 → references/01-foundations-and-decision-making.md
- ADT、コンポジションパターン、フロントエンドのコンポジション → references/02-adts-patterns-and-frontend-composition.md
- 段階的な移行とコードレビューのチェック → references/03-refactoring-and-review-checklist.md
- CIメトリクス、カスタム静的チェック、チーム導入 → references/04-metrics-and-adoption.md
デフォルトのワークフロー
-
現在のモデルを評価する
- 基底クラス、サブクラス数、階層の深さ、既知の問題点をリストアップする。
- 各変化軸を、データバリアント、振る舞い戦略、インフラストラクチャ関心事のいずれかに分類する。
-
メカニズムを選択する(デフォルト優先)
- 振る舞いのバリエーション、実行時の差し替え、または複数軸の変更には コンポジション を選択する。
- 閉じており、データを運ぶバリアントで、網羅的な処理が必要な場合は、ADT/sealed型 を選択する。
- 以下の条件がすべて満たされる場合にのみ 継承 を選択する: 真のis-a関係、Liskovの置換原則に準拠した代替可能性、安定した階層。
-
ターゲットとなる構造を設計する
- 最小限のコラボレーターコントラクト(協定)を定義し、変動性、複数の実装、独立したテストが必要な箇所にインターフェース/プロトコルを導入する。
- 必須のコラボレーターにはコンストラクタインジェクションを使用する。
- 継承の深さは浅く保つ(やむを得ないフレームワーク基底クラスを除き、目標は2以下)。
-
段階的に移行計画を立てる
- 最初に特性テスト(characterization tests)を追加する。
- 内部を置き換える前に、シーム(seam: インターフェース/ファサード)を導入する。
- 一度に一つのサブクラスのパスを移行する。
- パリティチェック(等価性検証)が通った後にのみ、不要になった階層の部品を削除する。
-
検証とガードレールを設定する
- チェックリストに基づくレビューを実行する。
- DIT(継承の深さ)、オーバーライド比率、デコレータの深さに関するCIメトリクス/チェックを追加する。
- フレームワークで要求される継承については、明示的な免除(waiver)を記録する。
不変のデフォルト
- デフォルトではコンポジションを優先する。
- コードの再利用だけを目的として継承を導入しない。
- コラボレーターのインターフェースは狭く保つ。
- サービスロケータパターンは避け、依存関係は明示的でなければならない。
- 深いデコレータスタック(>3)や広い継承ツリーは設計上のアンチパターン(コードの不吉な匂い)として扱う。
出力契約
ガイダンスを求められた場合、以下のセクションを順に返すこと:
- 判断: コンポジション、ADT、または継承。
- 理由: 結合度、変更軸、実行時の要件、テスト容易性への影響。
- 設計スケッチ: ターゲットとなるインターフェース/型とコンポジションルート。
- 移行手順: 検証ポイントを含む、小さく可逆的なステップ。
- レビュー/CIチェック: チェックリスト項目と測定可能なしきい値。
エッジケース
- フレームワークで継承が強制される場合: 継承は境界(フレームワークとの接点)に留め、内部はコンポジションで構成する。
- 言語に委譲(delegation)の糖衣構文がない場合: 転送(forwarding)用の小さなヘルパージェネレーター/テンプレートを使用する。
- パフォーマンスが重要なパス: 間接層(インダイレクション)を追加する前に、ディスパッチのオーバーヘッドを測定する。
- 小さく安定したドメイン: 網羅性が必要な場合、sealedな継承は許容されることがある。
例
入力: 「BaseReport に12のサブクラスがあり、新しいフォーマットを追加するたびにサブクラスが増えます。どうすればよいですか?」
出力(概要):
- 判断: 階層をコンポジション(Strategy + オプションでDecorator)に置き換える。
- 理由: 複数軸のバリエーションとオーバーライドの多いサブクラスは、分類学のドリフト(taxonomy drift)を示している。
- 設計スケッチ:
ReportRenderer、HeaderPolicy、FooterPolicy、OutputFormatterインターフェースを一つのReportServiceでコンポーズする。 - 移行: 特性テスト → Strategyの抽出 → サブクラスごとの移行 → サブクラスの削除。
- ガードレール: DIT警告 >3、オーバーライド比率警告 >0.5、デコレータの深さ警告 >3。