0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【リファクタリング】Extract Superclass(スーパークラスの抽出)

Posted at

1. 概要(Overview)

Extract Superclass は、複数のクラスに重複しているフィールド/メソッド を共通の親クラスへ切り出し、
継承関係を導入して 重複を排除 するリファクタリングです。

共通化により、コードの重複(Duplication)を減らし、一貫性と保守性 を高めます。

目的:

  • 重複ロジックを一箇所に集約(DRY)
  • 共通インタフェースを提供してクライアントを簡潔化
  • 将来の機能追加・不具合修正を容易にする

2. 適用シーン(When to Use)

  • 2つ以上のクラスに 同一/酷似のフィールドやメソッド が存在する
  • 「コピペ改変」が増え、変更が複数箇所に波及 している
  • 共通の抽象(概念)が見えているが、まだ明示されていない

よくある匂い:

  • Duplicate Code(重複コード)
  • Divergent Change(分岐的変更)
  • Shotgun Surgery(散弾銃のような修正)

3. 手順(Mechanics / Steps)

  1. 重複しているメンバ(フィールド/メソッド)を洗い出す
  2. 新しいスーパークラスを作成
  3. 共通メンバをスーパークラスへ移動(必要なら抽象メソッド化)
  4. 既存クラスをスーパークラスのサブクラスにする
  5. クライアントコードを親型(スーパークラス)に寄せられる部分は寄せる
  6. テスト実行・回帰確認

4. Kotlin 例(Before → After)

Before:2つのクラスに重複ロジック

class OnlineOrder(
    val id: String,
    val items: List<Double>,
    val shippingFee: Double
) {
    fun subtotal(): Double = items.sum()
    fun total(): Double = subtotal() + shippingFee
    fun description(): String = "OnlineOrder #$id"
}

class StorePickupOrder(
    val id: String,
    val items: List<Double>,
    val pickupStore: String
) {
    fun subtotal(): Double = items.sum()
    fun total(): Double = subtotal() // 受取なので送料なし
    fun description(): String = "StorePickupOrder #$id@$pickupStore"
}
  • subtotal() が重複
  • total() も一部重複しつつ分岐
  • iditems も共通の状態

After:スーパークラスを抽出

open class Order(
    val id: String,
    val items: List<Double>
) {
    fun subtotal(): Double = items.sum()

    open fun total(): Double = subtotal()

    open fun description(): String = "Order #$id"
}

class OnlineOrder(
    id: String,
    items: List<Double>,
    private val shippingFee: Double
) : Order(id, items) {

    override fun total(): Double = subtotal() + shippingFee

    override fun description(): String = "OnlineOrder #$id"
}

class StorePickupOrder(
    id: String,
    items: List<Double>,
    private val pickupStore: String
) : Order(id, items) {

    // total() は親のデフォルト(= subtotal)のままでOK

    override fun description(): String = "StorePickupOrder #$id@$pickupStore"
}
  • 共通の状態(id, items)と subtotal()Order に集約
  • total() はデフォルト実装を提供し、必要なサブクラスだけ上書き
  • description() も共通の枠を持ちつつ、サブクラスで差分化

5. 効果(Benefits)

  • 重複コードの削減 → 修正点が一箇所に
  • 共通の抽象を共有 → 型を揃えやすく、APIがスリムに
  • テスト容易性の向上(共通部は一度のテストで担保可能)

6. 注意点(Pitfalls)

  • 継承の乱用 に注意:共通化のためだけの不自然な継承は逆効果
    • ふるまいの共通化だけが目的なら Extract Interface も検討
    • 状態の継承が不要・関係が弱いなら Composition(委譲) を優先
  • スーパークラスの責務が増えすぎると再び Large Class 化しやすい
  • サブクラスが親の設計に 過度に縛られる リスク(将来の変更に硬い)

7. 実務Tips

  • まずは 最小公倍数の共通点 だけを親へ。早すぎる抽象化は禁物
  • オーバーライド増殖の兆候が出たら、設計を見直して Strategy/Composition
  • 既存呼び出し側は段階的に親型(Order など)へ寄せていく(安全なインターフェース収斂)

まとめ

  • Extract Superclass は、複数クラスの重複を共通の親へ集約するリファクタリング
  • 判断基準:状態とふるまいの“本当に”自然な共通性があるか?
  • 基本思想:DRY と整理された抽象。無理な継承は避け、場合により Interface / Composition を選ぶ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?