1. 概要(Overview)
Extract Subclass は、既存のクラスの中に 特定の機能やフィールドが一部のインスタンスでしか使われていない 場合に、
その部分を新しいサブクラスへ切り出すリファクタリングです。
「大きすぎるクラス(Large Class)」や「複数の責務を持っているクラス」を整理する時に有効です。
目的は以下の通り:
- クラスの責務を分離し、シンプル化 する
- 一部のオブジェクトにしか不要なメンバを整理
- 共通部分はスーパークラスに残し、差分をサブクラスへ
2. 適用シーン(When to Use)
- クラスが 大きすぎて複雑化 している
- フィールドやメソッドの一部が 特定のケースにしか使われていない
- その結果、クラスが「if文やnullチェックだらけ」になっている
- 派生的な概念が見えてきているが、まだ明確に分離されていない
よくある匂い:
- Large Class(巨大クラス)
- Divergent Change(分岐的変更)
- Refused Bequest(不要な継承)(逆パターンの検討要因にもなる)
3. 手順(Mechanics / Steps)
- クラスの中から、特定の条件でしか使われないフィールド/メソッドを特定
- 新しいサブクラスを作成
- 特定の責務をサブクラスへ移動(Push Down Field / Push Down Method の応用)
- 元クラスをスーパークラスとし、共通部分を残す
- クライアントコードを新しいサブクラスに切り替え
4. Kotlin 例(Before → After)
Before:1つのクラスに全責務を詰め込んでいる
open class Employee(
val name: String,
val type: String, // "engineer" or "salesman"
val commission: Double? = null // 営業だけが使う
) {
fun calculatePay(): Double {
return if (type == "salesman") {
basePay() + (commission ?: 0.0)
} else {
basePay()
}
}
private fun basePay(): Double = 3000.0
}
-
commissionは営業(salesman)専用なのにEmployee全体で保持 -
calculatePay()が if で分岐し、責務が混在している
After:サブクラスを抽出
open class Employee(
val name: String
) {
open fun calculatePay(): Double {
return basePay()
}
protected fun basePay(): Double = 3000.0
}
// エンジニアはシンプル
class Engineer(name: String) : Employee(name)
// 営業は手数料フィールドを追加
class Salesman(name: String, private val commission: Double) : Employee(name) {
override fun calculatePay(): Double {
return basePay() + commission
}
}
- 共通部分は
Employeeに残す - 営業だけが使う
commissionをSalesmanに移動 -
if分岐が消え、責務が整理された
5. 効果(Benefits)
- 大きすぎるクラスを分割し、読みやすくなる
- サブクラスごとに 責務が明確 になる
- 条件分岐の削減 により、コードがシンプル化
6. 注意点(Pitfalls)
- サブクラスを作りすぎると階層が複雑化する
- 将来的に「共通化した方がよいメソッド」が見つかる場合 → Pull Up Method を併用
- サブクラス分岐が複雑化するようなら Replace Type Code with Subclasses の導入も検討
まとめ
- Extract Subclass は「大きなクラスの一部責務を切り出してサブクラス化する」リファクタリング
- 判断基準:このフィールド/メソッドは全インスタンスで必要か? それとも一部だけ?
- 基本思想:クラスを役割ごとに整理して、条件分岐や不要な状態をなくす