オブジェクト指向で継承は使うべきではない?

by tanabe on September 26, 2005

DB Magazine 10月号(古!)の『柔軟なシステムを作成するためのC♯によるオブジェクト指向開発の実践』という記事で、少し気になる記述があったので、ちと書いてみます。

「なお、継承は、ポリモーフィズム(多態性)を利用するときのみ使用するのが基本となっている。」
「オブジェクト指向による設計では、既存クラスを継承することはあまりない。」

ということで、たしかにJavaやC#によるプログラミングでは委譲によって実装の再利用を行うことが定石となっています。継承はポリモーフィズムのための手段でしかないのかもしれません。記事はC#に関するもので、その意味では問題視するほどの記述ではありません。ただ、これだけ言い切られてしまうと、オブジェクト指向入門的な内容とも相まって誤解する人が増えるのでは、と気になりました。

本当に「継承は悪で、委譲は正義」なのか?

これは言語の制約によるものです。ここで本来やりたいことは実装の継承です。
言語が十分に実装の継承を生かす仕組みを用意できなかったので、仕方なくプログラマが実装の継承を模した形で「委譲」という方法を取っているだけなのです。

問題は、「実装の継承は危険だ」ということではなくて、「安全な実装の継承の仕組みが言語からサポートされていない」ということです。

「仕様の継承」と「実装の継承」

継承には二つの考え方があり、仕様の継承と実装の継承があります。そして実装の継承の強力さを十分に生かすには、多重継承ができることは本来望ましいのです。

JavaやC#では仕様の継承を重視し、それをinterfaceという概念で表しました。複数interfaceの継承を許すことで、仕様の継承(関係の表現)としては柔軟性を残しました。一方で、実装の継承は継承関係の複雑化やダイアモンド継承のような危険を避けるために、単純継承のみに制限をしています。

C++では実装の継承において多重継承を許し、リスクを享受しました。一方で仕様の継承を表現するために「public virtualで中身のない(=0)関数」というinterface相当の宣言を生み出しました。

C++の問題は明らかです。多重継承によって発生するリスクがフォローされていないことです。またJava, C#での問題は、リスクを抑える代償として実装の継承の持つ強力さがスポイルされていることです。

Mix-inという考え方

この多重継承の問題を解決する一つの方法として、Mix-inというものがあります。

Mix-inは、多重継承の一つの使い方です。多重継承に制限をかけ、機能は生かしつつ、安全性を高めようというものです。

日経Linux 2005年7月号の連載「まつもと直伝プログラミングのオキテ 第3回 多重継承の光と影」からMix-inの解説を抜粋します。

Mix-inというのは元々Lisp界で始まった多重継承の使い方です。Mix-in手法には次の2つの条件があります。

  • 通常の継承は単一継承に限る
  • 2つめ以降の継承は,Mix-inと呼ばれる抽象クラスからに限定する

Mix-inクラスは以下のような特徴を備えた抽象クラスです。

  • 単独でインスタンスを作らない
  • 通常のクラスから継承しない

これらの規則に従うことで,クラス階層は単一継承と同じ木構成になりますし,機能の共有を実現するには,共有する機能だけを持つMix-inをクラス階層木に「差し込む」ことで達成できます。

このMix-inを使うと、継承関係がすっきりとします。ただし、スーパークラスとの属性(メソッド名など)の衝突は課題として残ります。それでも、安全性と強力さのトレードオフを考えると、非常に魅力的な手法です。

Java, C#では活用できませんが、C++ではやり方次第で実現できます。また、Rubyでは言語仕様としてMix-inをサポートしており、そのパワフルさが言語標準のAPIでもフル活用されています。

継承はけして悪いものではありません。ただ、使い方に注意をしてメリットを十分に生かし、危険は少しでもコントロールしてやる必要があるだけです。

・・・と、Ruby界隈で見かけた継承に関する話をまとめてみました。自分メモにも使えるし。おかしなところがあれば、バシバシ突っ込んで頂けると理解を深めることができるので助かります。(元ネタは社内で回覧しているDB Magazineの記事紹介。一部内容を修正。)