1. 概要(Overview)
Replace Inheritance with Delegation は、
サブクラスがスーパークラスに 過剰に依存している、または継承関係が 不自然/誤用 されている場合に、
その継承をやめて 委譲(Delegation) に置き換えるリファクタリングです。
- 継承:is-a 関係(例:Dog is an Animal)
- 委譲:has-a 関係(例:Team has a Player)
誤った継承を委譲へ置き換えることで、結合度を下げ、柔軟性を高める ことができます。
目的:
- 不自然な継承を取り除き、正しい関係へ修正
- クラス間の結合度を下げ、変更に強くする
- 再利用性・テスト容易性を高める
2. 適用シーン(When to Use)
- 継承しているが、本質的に is-a ではなく has-a の関係
- サブクラスがスーパークラスの機能の一部しか使わない
- サブクラスがスーパークラスの実装に強く依存し、変更に脆い
- 将来的にスーパークラスの変更がサブクラスへ波及してしまうリスクがある
よくある匂い:
- Refused Bequest(不要な継承)
- Inappropriate Intimacy(不適切な親密さ)
- Speculative Generality(過剰な一般化)
3. 手順(Mechanics / Steps)
- 継承関係が「不自然」か確認(is-a ではなく has-a か?)
- サブクラスにスーパークラスのインスタンスをフィールドとして保持
- サブクラス内で必要な処理を 委譲メソッド 経由で呼び出す
- スーパークラスの継承を削除
- テストを実行して、挙動が変わらないことを確認
4. Kotlin 例(Before → After)
Before:不自然な継承
// Stack が Vector を継承している(Java の古い実装例に近い)
class MyStack<E> : java.util.Vector<E>() {
fun push(item: E) = this.add(item)
fun pop(): E = this.removeAt(this.size - 1)
}
-
MyStack
はVector
の全機能を継承してしまっている - しかしスタックは「Vector のすべての振る舞い」を持つべきではない
- 本質的には Vector を内部で利用するだけ で十分
After:委譲へ置き換え
class MyStack<E> {
private val data = mutableListOf<E>() // 委譲対象
fun push(item: E) = data.add(item)
fun pop(): E = data.removeAt(data.size - 1)
fun isEmpty(): Boolean = data.isEmpty()
}
-
MyStack
はMutableList
を has-a で保持 - 本当に必要な操作だけを公開し、余計な機能は隠す
- 結果、
MyStack
は「Vector ではなく Stack」であることが明確になる
5. 効果(Benefits)
- 継承の誤用を防ぎ、責務が明確化
- 内部実装を自由に差し替え可能(List → ArrayDeque 等)
- サブクラスがスーパークラスに縛られなくなり、保守性・柔軟性が向上
- 公開 API が必要最小限になり、誤用を防げる
6. 注意点(Pitfalls)
- 委譲メソッドを多く定義する必要があり、コード量が増える可能性
- 継承の方が自然な場合(真の is-a 関係)には逆効果
- 過度に委譲を導入するとラッパークラスだらけになる(YAGNI を意識)
まとめ
- Replace Inheritance with Delegation は、不自然な継承を「委譲」に置き換えるリファクタリング
- 判断基準:その関係は本当に is-a か? それとも has-a か?
- 基本思想:柔軟性・カプセル化を高め、依存関係を正しく整理する