nounai.output(spaghetiThinking);

趣味と実益を兼ねて将棋プログラム(研究ツールなど)を作ってみたいと思う私の試行錯誤とか勉強したことを綴ってゆく予定です。 主目的はプログラミングの経験値稼ぎですが、コンピュータ将棋の製作も目指してみたいとも考えています。

5/16(Thu) - 継承/インタフェースの使いどころについて

少しはオブジェクト指向の理解も始まりだした、かな?

継承・インタフェースの使い時

下の方で書きますが、クラス継承とは「性質のAND」ということができます。定義をより厳密にし、親→子孫の関係がジェネラル→スペシャルになっていくのが継承、ということになります。

クラスが「性質」、言い換えると「本質」を定義するものだとすれば、そのクラスが自身の本質にそぐわない仕事をするのは本来おかしな話です。実際のオブジェクト指向においては、1つのクラスがメソッドを持ちすぎている・多くの責任を持っている、といった事態がこれに相当すると思います。これらはリファクタリングでも改善対象として挙げられています。既存クラスを継承して作成したクラスが、親より「ジェネラル」な方向に行ってしまったらそれはNGなのです。

しかしながら、クラスの本質とは外れるけれども、その役割を遂行するために補助的に必要な「機能」というのが必要になることが往々にある。本質だけに限定しては、できることが極端に制限されてしまいます。インタフェースはそうした本質部分以外の「機能」を与える役目で用いることができます。

まとめると、継承はクラスをよりスペシャルな存在にし、より実際に想定する利用シーンにフィットするようにしていくこと。インタフェースは本質部分とは合致しないが、目的の達成のために補助的に必要な機能を満たすためのものと位置づけることができるのではないかと思います。

外延と内包

ここから先は雑学(冗長)です。

哲学の分野の話になりますが、「外延」と「内包」という言葉があります。どういう意味かというのをwikipedia - 定義より引用すると、

ある概念を類と見たとき、その類に含まれる種の全てを外延 (Extension)、その種に共通な性質を内包 (Intension) という。種を全て示すことを外延的説明、共通な性質を示すことを内包的説明という。

・・・と、あります。wikiに載っている数の集合による説明はわかりやすい題材だと思います。

重要なのが、クラスの継承というのは内包の継承である、ということです。このことを最初に知ったのはiwatamさんのiwatamの個人サーバ - 継承にかかわる諸問題を見てからなのですが、「そうなのか!」と、思ったはいいのですが結局ちゃんと納得はできていませんでした。※今も正直なところ怪しいです。

内包の継承とは「性質」の継承です。「このクラスは本質的に何をしてくれるクラスなの?」という問いに対する答えがそのクラスの性質、ということになります。「性質」は「特性」のような意味合いで使うこともありますが、ここで言う性質は「特性」とはまたちょっとニュアンスが違います。

自前でクラスを作る時の想像しながらこの話を考えると、抽象的な話をさらに抽象したみたくなって掴みどころがありませんので、ここではwikipediaにもある数の集合で考えてみます。

まず、ここで継承階層の基底を「自然数」とします(javaならobjectに相当します)。これの外延は{-1000, -999, ... -1, 0, 1, 2...}とまぁ、いろいろありますね。
で、この基底クラスを継承してみます。「自然数」の性質を持ちながら別の性質・・・例えば「正の数」という性質を持つ派生クラスを定義しましょう。継承は性質のANDなので、その外延は{0,1,2,3,4...}と絞られます。
この派生クラスをさらに継承します。今度は「偶数」という性質を持つ派生を考えます。この派生クラスの外延は{0,2,4,6...}と更に絞られます。

内包を継承していくことで性質はより具体的に定義され、それに合致する具象、つまり外延は絞り込まれていきます。

では「外延」を継承するとどうなるか?これも数の集合で考えると理解しやすいです。

基底として、外延 {0,1,2,3...,10}を考えます。基底なので定義も貧弱です。これに当てはまる内包は「10以下の正整数」という所でしょうか。基底を継承し、{11,12,13,......}という外延を加えてみます。できあがる派生は{0,1,2,3......}となります。派生を表す内包は「正整数」です。
継承した分外延は充実しました。対して、内包は基底より抽象的・一般的なものになり、性質を定義する力が貧弱になります。外延の継承はこういうことになります。

外延の継承ではかなり不都合なことがあります。
外延を継承する時は「とある外延の集合」がANDされていくわけです。そして、その外延にフィットする内包が存在するはずですが、継承してできた派生物を表す内包って物凄く命名しづらいんです。
仮に{0, 0.1 , 0.2 , ... 1.0}の外延をベースとして、新たに{-100, -98, -96,..., 0}という外延を加えた派生を考えてみましょう。これを表現できる内包って一体なんでしょうか?外延同士で共通して見出せる「性質」が見当たりません。「実数」であれば確かに派生物の外延はカバーしていますが、「実数」の外延は今作った「派生物の外延」と明らかにイコールではありません。
「-100以上の負整数もしくは0から1までの0.1刻みの少数」とすれば一応表現は可能ですが、2つの性質のORとして定義される以上その派生君が何者なの?という本質は掴みづらいものになってしまいます。結局、つじつま合わせのむちゃくちゃカオスな内包になってしまうしか道がありません。

今の話から、「外延」ありきではそこから内包を導くことは容易ではない、または不可能に近いことがわかります。継承するだけであれば簡単ですが、それに見合う内包を導き出すことが難しく、つじつま合わせ的にならざるを得ない。手元にある外延集合にジャストフィットする内包を導かなければならないのです。
「内包」から始めた場合は割と自然です。内包=性質ですから、性質をANDした時にそれが矛盾しているかどうかは継承するまでもなく判断できます。そして、「内包」から「外延」の導出は簡単です。外延となる全ての解を求める必要もありません。

また、内包の継承は「性質のAND」なので、基底クラスの性質がカバーしていない外延集合を、派生が持つことは許されませんJavaの例で言うならば、「出力」の性質であったはずのOutputStreamを継承して「入出力」の性質を持つ派生を作ろうとするようなものです。パッと見性質の記述が増えたので内包継承は破綻していないように思われますが、間違いです。このエセ派生クラスでは「"入力という性質を併せ持つ出力"を行う性質」を持たなくてはなりません。日本語としてもおかしいですね。ちなみに、「入力」と「出力」を場合に応じて使い分けできること、これは性質のANDではなく、性質のORです。ANDだから派生クラスの性質は親クラスの性質の部分集合になってなきゃおかしい。
両者の外延を比較してみても、この継承が内包継承ではなく外延継承であることがわかると思います。外延が減らない継承は正しい継承ではありません。

結論:クラスの継承では、定義がより厳密になっていなければならない、ということです。曖昧になってしまったらそれは本来のクラス継承ではありません。