Featured image of post 「継承より委譲」の正確な意味

「継承より委譲」の正確な意味

原義は Favor Composition over Inheritance である。合成・委譲・転送の用語を整理する

要点

  • 「継承より委譲」という表現がよく使われるが、原義は “Favor Composition over Inheritance” — 「継承より合成を優先する」である。
  • has-a(オブジェクトを保持する)という構造は「委譲」ではなく「合成(Composition)」と呼ぶ方が誤解が少ない 1
  • Composition は構造(has-a)、Delegation は設計手法、Forwarding はその実装である。

GoF の定義

GoF は次のように述べている。

Delegation is a way of making composition as powerful for reuse as inheritance.
委譲は、合成を継承と同じくらい強力な再利用手段にする方法である)

Design Patterns: Elements of Reusable Object-Oriented Software

合成(has-a)だけでは構造の関係に留まるが、委譲を組み合わせることで振る舞いの再利用が可能になる。これが「継承より合成」の本質である 2

3 つの概念

レイヤー 概念 説明
構造設計 Composition(合成) has-a 関係。オブジェクトを保持する構造
設計手法 Delegation(委譲) 自身の責務の一部を、保持したオブジェクトに任せる
実装 Forwarding(転送) 呼び出しを転送するコード

Delegation は文献によって使い方が揺れる。Forwarding と混用されることも多い 3

コード例

1
2
3
4
5
6
7
interface Engine {
    fun start()
}

open class DefaultEngine : Engine {
    override fun start() { /* ... */ }
}

合成による再利用(has-a)

1
2
3
4
5
6
7
8
// 合成:Car has-a Engine
// 委譲:Car.start() の責務を Engine.start() に任せる
// 転送:実装は engine.start() への forwarding
class Car(
    private val engine: Engine = DefaultEngine(),
) {
    fun start() = engine.start()
}

CarEngine保持し(合成)、start() の責務を任せ(委譲)、engine.start()呼び出す(転送)。3 つのレイヤーがそれぞれ異なる役割を担っている。

Engine の実装を差し替え可能にすれば、テスト容易性も向上する。

継承による再利用(is-a)

1
2
3
4
5
6
7
// DefaultEngine を継承して再利用
class TurboEngine : DefaultEngine() {
    override fun start() {
        super.start()
        // ターボ固有の処理
    }
}

super.start() は継承階層内の呼び出しであり、別のオブジェクトに責務を任せているわけではない。これは委譲ではない。

まとめ

「継承より委譲」ではなく「継承より合成」が原義である。

  • 合成(Composition) は構造の話(has-a)
  • 委譲(Delegation) は合成と組み合わせて再利用を可能にする設計手法
  • 転送(Forwarding) はその実装

この 3 つを区別するだけで、設計の議論における用語の曖昧さは大きく減る。


  1. 「継承より合成」は「継承より委譲」「継承より構造」と表されることもある。本記事では原義に近い「合成」を用いる。 ↩︎

  2. GoF は継承を禁止しているわけではない。合成+委譲を「継承に匹敵する再利用手段」として位置づけている。 ↩︎

  3. 一般的な用法では「責務を別オブジェクトに任せる」と記され、設計なのか実装なのかを区別しないことが多い。 ↩︎

Hugo で構築されています。
テーマ StackJimmy によって設計されています。