1. 概要(Overview)
Extract Class は代表的なリファクタリング手法の一つです。
あるクラスが肥大化し、複数の責務を抱え込んでいる(God Class 化しつつある) 場合、その一部のフィールドやメソッドを切り出して、新しいクラスとして独立させます。
Extract Class の目的は、責務を分割し、クラスごとの凝集度を高めることで、理解しやすく拡張しやすい設計にすることです。
2. 適用シーン(When to Use)
- クラスのコード量が増えすぎて、1,000行を超える巨大クラス になっている
- クラスが 複数の概念や責務を同時に管理 している
- 特定のフィールド群が、他のフィールドとは独立した振る舞いを持っている
- あるメソッド群が、クラス全体ではなく 特定の役割だけを操作 している
よくある匂い:
- Large Class(巨大クラス)
- God Class(大泥棒)
- Divergent Change(分岐的変更)
3. 手順(Mechanics / Steps)
- クラス内のフィールドやメソッドを観察し、まとまりを見つける
- 抽出対象のフィールドとメソッドを新しいクラスに移動
- 元クラスに新クラスのインスタンスを保持させる
- 呼び出しを新クラス経由に変更
- 不要になったコードを整理し、テストを実行
4. Kotlin 例(Before → After)
Before:責務を抱え込みすぎているクラス
class Person(
val name: String,
val age: Int,
val street: String,
val city: String,
val zip: String
) {
fun printProfile() {
println("$name ($age)")
}
fun printAddress() {
println("$street, $city, $zip")
}
}
-
Personが 個人情報 と 住所情報 を同時に管理 - 責務が混ざっていて膨張しやすい
After①:Address クラスを抽出
class Address(
val street: String,
val city: String,
val zip: String
) {
fun printAddress() {
println("$street, $city, $zip")
}
}
class Person(
val name: String,
val age: Int,
val address: Address
) {
fun printProfile() {
println("$name ($age)")
}
}
→ Address を独立させることで責務が分離され、構造が明確に。
After②:段階的に移行(委譲メソッドを残す)
class Person(
val name: String,
val age: Int,
private val address: Address
) {
fun printProfile() {
println("$name ($age)")
}
// 既存の呼び出しを壊さないため委譲
fun printAddress() = address.printAddress()
}
→ 既存コードを壊さずに移行可能。
5. 効果(Benefits)
- 責務が整理され、凝集度が高い小さなクラス に分割できる
- コードが理解しやすくなり、保守・拡張が容易になる
- 再利用性が向上(例:
Addressを他のエンティティでも利用可能) - 単体テストが簡単になり、テスト対象が明確化
6. 注意点(Pitfalls)
- 抽出が細かすぎると、逆にクラスが増えすぎて管理が大変になる
- 責務の境界が曖昧だと、どこまで抽出すべきか迷いやすい
- 既存 API を公開している場合、委譲メソッド を残すなど互換性維持が必要
まとめ
- Extract Class は「1つのクラスが持ちすぎた責務を分割する」リファクタリング
- 判断基準:フィールドやメソッドに自然なまとまりがあるか?
- 基本思想:責務を分けて、理解しやすく高凝集な設計にする