継承にかかわる諸問題

「継承」はオブジェクト指向ではよく話題になり、また問題視されます。しかしそれは使い方が間違っているからです。

継承と機能

今まで何回も繰り返してきたように、継承と機能は関係のないものです。しか し残念ながらいまだに「機能を継承する」という形で継承を説明している本が 多くあります。そして、プログラミングから入った人々には機能のことしか頭 にないことがよくあります。

「継承とは既に定義されているクラスを基に、拡張や変更を加えた新しいクラ スを定義すること」というような説明がよくなされます。こんな説明をする人 には「何を拡張したり何を変更したりするのですか?」と質問してみましょう。 「属性やメソッドの拡張や変更だ」と言った人は機能のことしか頭にない人で す。こんな考え方が次のようなトラブルを生みます。

covariance-contravariance問題

継承について問題となる事項にcovariance-contravariance問題というものが あります。プログラミング言語ではメソッドの引数の型の問題として語られま すが、ここではプログラミング言語の用語を出さずに同じ問題を考えてみましょ う。

例えば「犬はドッグフードを食べる」と定義したとしましょう。しかし、犬の ジョン君は安いドッグフードをあげても絶対食べません。だから、犬のサブク ラスに「ぜいたくな犬」というのを追加して、「ぜいたくな犬は高いドッグフー ドしか食べない」と定義したとしましょう。この場合、「犬」の一部である 「ぜいたくな犬」は、「ドッグフード」の一部である「高いドッグフード」し か食べません。これをcovarianceルールといいます。

それに対して、ポチは汁かけご飯でもサンマの骨でも喜んで食べます。だから、 犬のサブクラスに「貧乏な犬」というのを追加して、「貧乏な犬はドッグフー ドも汁かけご飯もサンマの骨も食べる」と定義したとしましょう。この場合、 「犬」の一部である「貧乏な犬」が食べられるものは「ドッグフード」だけで はなくもっと広い範囲の「食物」です。これをcontravariantルールといいま す。

この問題は、クラス(あるいは型)の継承の種類が明確になっていないから起き る問題です。covariantルールの例にある「犬はドッグフードを食べる」とい うのは内包、つまり一般的なイメージとして見ています。そしてぜいたくな犬 はドッグフードの中でも高いものしか食べない、と定義しています。

それに対してcontravariantルールでは「犬はドッグフードを食べる」という のを外延、つまり正確な定義として見ています。「ドッグフードを食べないも のは犬ではない」というわけです。その上で、それに対して「汁かけご飯もサ ンマの骨も食べられる」という機能が追加された犬として「貧乏な犬」を定義 しています。

covariantルールの方が直感的に正しいと思えるでしょう。それがcovariantルー ルの利点です。 しかしcovariantルールでは実用上の問題も存在します。たくさんの犬を預かっ てきた時に、餌をやる時にこの犬が何を食べるかをいちいち確認しないといけ ないのです。つまり、「犬はドッグフードを食べる」という情報がほとんど何 の役にも立たないわけです。contravariantルールが適用できるのであれば、 どの犬にでもとりあえずドッグフードを与えておけばすみます。

これはどちらが正しい/正しくないという問題ではありません。継承の種類を 明確にしないのが問題なのです。クラスや継承を使う際には、それが内包なの か外延なのか機能なのかをはっきりさせましょう。