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?

【リファクタリング】第18回 並行継承階層(Parallel Inheritance Hierarchies)

Posted at

はじめに

並行継承階層(Parallel Inheritance Hierarchies) とは、
あるクラス階層を拡張するとき、必ず別のクラス階層にも同じように新しいサブクラスを作らなければならない状態 を指します。

例:

  • ShapeRectangle を追加したら、必ず ShapeRenderer にも RectangleRenderer を追加しないといけない
  • Document に新しいサブタイプを追加すると、DocumentParser にも対応するサブクラスを追加しなければならない

これは 継承の構造が強く結びついている ために発生するコードの悪臭です。


18.1 特徴

  • 片方の継承階層にクラスを追加すると、必ずもう片方にもクラスを追加 しなければならない
  • 実装が 二重管理 になり、変更コストが増大
  • 「A の階層」と「B の階層」が 常に並行して成長 してしまう

18.2 解決手法

  • 継承から委譲へ(Replace Inheritance with Delegation)
    → 階層を作らず、オブジェクトに処理を委譲する
  • 共通インターフェースの導入
    → 共通の抽象を定義し、2つの階層を切り離す
  • ダブルディスパッチ/Visitor パターン
    → 並行階層の分岐をまとめて扱う
  • 戦略パターン(Strategy Pattern)
    → 動作部分を戦略として分離し、継承ではなく組み合わせで対応

18.3 Kotlin 例

Before:並行継承階層

// 図形ごとにレンダラーも並行して追加する必要がある
open class Shape
class Circle : Shape()
class Rectangle : Shape()

open class ShapeRenderer
class CircleRenderer : ShapeRenderer()
class RectangleRenderer : ShapeRenderer()
  • Circle を追加したら CircleRenderer も追加しなければならない
  • 変更が「並行」して増える → Shotgun Surgery に近い構造

After①:委譲で解消

interface Renderer {
    fun render()
}

abstract class Shape(protected val renderer: Renderer) {
    abstract fun draw()
}

class Circle(renderer: Renderer) : Shape(renderer) {
    override fun draw() = renderer.render()
}

class Rectangle(renderer: Renderer) : Shape(renderer) {
    override fun draw() = renderer.render()
}

→ 描画処理を Renderer に委譲。新しい Shape を追加しても Renderer 側は変更不要。


After②:Visitor パターン

interface ShapeVisitor {
    fun visit(circle: Circle)
    fun visit(rectangle: Rectangle)
}

abstract class Shape {
    abstract fun accept(visitor: ShapeVisitor)
}

class Circle : Shape() {
    override fun accept(visitor: ShapeVisitor) = visitor.visit(this)
}

class Rectangle : Shape() {
    override fun accept(visitor: ShapeVisitor) = visitor.visit(this)
}

class RenderVisitor : ShapeVisitor {
    override fun visit(circle: Circle) = println("Render Circle")
    override fun visit(rectangle: Rectangle) = println("Render Rectangle")
}

Shape 階層と Renderer 階層を分離し、Visitor で拡張


18.4 実務での指針

  • 並行して増えるクラス階層は 設計の匂い
  • 「is-a」ではなく「has-a」や「can-do」で表現できないか再検討
  • Strategy / Visitor / Delegation を使うことで、階層の爆発を防ぐ
  • Kotlin の場合は sealed class + when で網羅性チェックを利用するのも有効

まとめ

  • Parallel Inheritance Hierarchies は「二重管理」が問題の根源
  • 解決には 委譲 / Strategy / Visitor を活用
  • 基本思想:継承は増殖を招きやすい。委譲や抽象化で切り離して管理する

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?