継承とインターフェイス

おはようございます。毎度質問ばかりで申し訳ないのですが。以前後輩に、継承はインターフェースみたいな使い方もできるけどどう使い分ければいいですか?って質問された時にうまく答えられなくて。上で言ってる使い方っていうのは親のクラスやインターフェースを引数に持って子のクラスや実装クラスを渡すみたいな使い方です。僕は上のような書き方はどちらでも書けるけど継承はそういう使い方がそもそも目的ではなく、同じような処理や構造を使い回すために利用するのかなと思っていて、そういう考えで使うならインターフェースを使うべきなのかなと思っていたのですが、まったく自信がなくて答えられなくて、今度コンタクトとれたら教えて頂きたいと思ってました。構文的な違いは一応調べてみて、差異はいろいろあったのですが、同じような使い方ができる時の使い分けの考え方について教えて頂きたいです。

…ネタが降ってきたw

物理的(言語的)な制約としては、その理解で良いと思います。C#をちょっと外れてC++の場合、C++にはインターフェイスという直接的な概念が無いのですが、純粋仮想関数(つまり、実装を持たない、表面的な構造だけを示したメソッド)の集合をクラスで表現した時、それがインターフェイスとなります。つまるところ、普通のクラスのメソッド群から「実装」を取り除き、表面的な見た目(メソッドシグネチャ)だけにしたものが、インターフェイスです。

で、インターフェイスとクラス継承の違いですが:

  • クラスの場合は、基底クラスの実装(振る舞い)を「継承」して、新たな振る舞いを付け加えたり、差し替えたりする事が出来る。
  • インターフェイスの場合は、表面的な見た目だけを定義し、実際に使う側からはその「見た目」が同一に見えるように強制出来る。

ぶっちゃけ、よく使う説明は:

仮面ライダーのお面がインターフェイスだと思いねぇ(ライダーマンが良いかも)。お面を誰がかぶっても、はた目にはライダーにしか見えない。ライダーに見せかける事によって、ショッカー軍団が襲ってくる(機能)。ライダーのお面をかぶらないタダの人間は、ショッカーの敵に適合しないので襲われない(本当か?)

「中の人」が誰であれ、お面をかぶると「仮面ライダー機能」に適合する。誰であれ、というのが、「どんな実装のクラスでも」と言い換えられます。極端な話、ゴリラがかぶっても良い。

しかし、基底クラスを継承する場合は、少なくともかぶる人が類似性(具象クラス)を持っている必要があります。中の人は少なくとも人間でなければならず、しかも改造人間でなければならないという制約が生じます。

更に、ウルトラマンのお面をかぶることも出来ます。それも同時に。ウルトラマンのお面に反応する敵は、仮面ライダーのそれとは異なります。違いは、顔の作り、「メソッドシグネチャの異なるメソッドの集合」という事になります。これを同時にかぶった人間は、フィクションでも実在しませんが、ある時はライダーに変身し、ある時はウルトラマンになる、というような、「仮面ライダーvsウルトラマン」的なネタがOKって事です。

#ちょっと嘘があります。お面を動的に切り替えれる訳ではありません。コンパイル時には決定されている必要があります。

もし、どちらで実装すべきか悩む場合は、インターフェイスを使用する前提にしておく事をお勧めします。その方が自由度が高い為です。多くのメソッドをそのまま派生クラスで流用したい場合に、基底クラスを使用したくなりますが、そもそもそんなに沢山のメソッドが単一のクラス内に定義されていることが問題です。

身近な例ではWPFやWindows Formsのクラス群が思い浮かびますが、あの実装は様々なトレードオフの検討の上で、あのような構造になっている、特殊な例です。

また、インターフェイスで分離を行う事で、副次的な良い効果があります。一つは、実装を明確に分離できる事です。基底クラスの実装に依存していると、派生クラスの実装がどのように基底クラスに依存しているのかを理解することが難しくなります(基底クラスと派生クラスの高度な分離は、オブジェクト指向版gotoだと言う記述をどこかで見た気がしますが、その通り)。インターフェイスで実装を分離していると、別のクラスに実装が分離されるので、正しくカプセル化を施していれば、機能の境界線ははっきりします。

もう一つは、自動化ユニットテストを記述しやすくなる事です。ユニットテストを記述する場合は、ファイルやネットワークやデータベースなどの「外部リソース」を、テストから分離しておかないと、テスト結果が不安定になります(テスト開始時には常にイコールコンディションとなるようにしないと、テスト結果がまちまちになってしまうが、ファイルやデータベースの事前準備を自動化するのは面倒)。

インターフェイスで分離されていることで、これらの外部リソースアクセスを、全く独自の、実際と異なる、しかしながら表面的には同一に見える実装に差し替えてテストすることが出来ます。インターフェイスのお面をかぶる「中の人」が誰でも良い、という特性を簡単に生かす事が出来ます。もし基底クラスを使用すると、基底クラスの機能に縛られ、全く異なる独自実装に置き換えることが難しいかもしれません。

C#の場合、メソッドはデフォルトで非仮想関数です。そのため、オーバーライドは出来ません(newを使用すると、名前が同じでも異なるメソッドとして扱われる)。インターフェイスであれば、最悪はラッパークラス(右から左にメソッド呼び出しを転送)を実装すれば、回避出来ます。

複数のインターフェイスを実装する場合に、たまたまメソッドシグネチャが同一であっても実装を分けたい場合には、インターフェイスの明示実装という手法を使うことで実現出来ます。

なお、CLRに限って言えば、仮想関数の呼び出しよりもインターフェイスメソッドの呼び出しの方がコストが高かったと思います。勿論、アプリケーション全体を通して、インターフェイスメソッドの呼び出しのコストがどの程度の割り合いなのかを評価しなければ、一概に悪いとは言えません。むしろ、その問題だけを引き合いに出してインターフェイスによる分離を行わないのは、本末転倒でしょう(そして、改善するなら測定してからにしましょう)。

#で、この項のネタは、先日見てきた「これ」に引っ張られてたりするw
#キャラクターネタにすると、諸般の事情で絵が描けないのが後の祭りだった…

投稿者:

kekyo

A strawberry red slime mold. Likes metaprogramming. MA. Bicycle rider. http://amzn.to/1SeuUwD