前置き
クラス継承の利点とは何か?(5年間考えた)の続編です。
大変ありがたいことに、いくつかコメントをいただいたので再考しました。
※ コメント、マサカリ歓迎です!
継承のメリットを考える
ばばばーーっと挙げてみました。
※「継承」には「インターフェイスの実装」も含めて考えていきます。
オブジェクトとしての再利用性向上
サンプルソースで見てみましょう。
例としてユーザー検索のインターフェイスを定義してみます。
interfaceIUserSearch{UserFetch(Guidid);}
さて、定義したインターフェイスを実装しましょう。
classUserSearch:IUserSearch{publicUserFetch(Guidid){/* DBなどから検索して、Userクラスを返してあげる処理を実装します。 */}}
これで再利用の準備が完了です。
インターフェイスを定義する分、若干コード量は増えますが簡単ですね。
使い方も見てみましょう。
classFoo{privateIUserSearchUserSearch{get;}publicFoo(IUserSearchuserSearch){// コンストラクタで設定UserSearch=userSearch;}publicvoidBar(Guidid){varuser=UserSearch.Fetch(id);/* blah blah blah */}}
ほかのクラスでも使いたい場合、同じようにプロパティを用意すればOKです。
コピペよりも断然取り回しがよくなったと思いませんか?
「え、継承いらなくね? クラスだけで共通化できるじゃん。」と思われたかもしれません。
確かに、先ほどの例だけを見るとそうですね。
では、単一クラスではできない利点を見ていきましょう。
実装への依存を減らせる(DI)
ところで「Foo」クラス内で「new UserSearch()」していないのに気づきましたか?
特定のクラスに対してではなく、インターフェイスの定義のみに依存するようになっています。
つまり「FetchにIdを渡したらUserが返ってくる」ことだけを期待しているのです。
実装への依存が減らせたということは、一見関係ない修正に引きずられてバグった!なんてトラブルから解放されるでしょう。(がっつりバグってたらさすがにダメです)
詳細については、下記の神記事などをご参照ください。
参考記事:DI (依存性注入) って何のためにするのかわからない人向けに頑張って説明してみる
単体テストの利便性向上
「Foo」クラスが「UserSearch」クラスへ依存しなくなったことで、テスト用のスタブも容易に利用できます。
たとえば、検索失敗した場合のテストをしたい場合、こんなスタブを用意すればいいでしょう。
classStubSample:IUserSearch{publicUserFetch(Guidid){returnnull;}}
テスト時は「UserSearch」クラスのかわりに「StubSample」クラスを渡してあげると、
「Bar」の引数にかからわらず、検索できなかった場合の動作を確認できるというわけです。
これは自動テストがはかどりますね
実装クラスに機能を保証させられる
IXxxxableみたいなインターフェイスを実装したクラスの場合、
このクラスは「Xxxx」できますよー!って宣言しているんですね。
.Net標準の
- IEnumerable(数えられるよ ※ForEachできるよ)
- IDisposable(破棄できるよ ※usingできるよ)
などが代表的です。あこれメリットというより使い方か
橋渡ししてくれる
Interfaceとは
(二者間の)境界面、接点、共通の問題、インターフェイス
これがもとの意味なんですね。
つまり、オブジェクトがオブジェクトを呼び出すのを橋渡ししてくれる存在というわけです。
結果として疎結合の実現や、依存関係の整理に一役買ってくれるわけですね。
ありがとうインターフェイス!
…で、結局「クラス」継承の利点は?
ここまで、インターフェイスの話ばかりを述べました。
この記事の出発点はクラスの継承なのにごめんなさい
いろいろ考えましたが、前回の記事から
下記の 差分プログラミング1みたいな記述を省いたものが答えではないかなと思います。
親クラスを修正すると子クラスにも波及して、修正箇所が減らせるよ親クラスに共通関数を定義できるよ結果的にコード量を削減できるよ
つまり「is-a関係をコードで具体的に表現できること」がクラス継承にしかないメリットだと思います!
「is-a関係」を表現しないのであれば(「is-a関係」でないのなら)
先述のとおりインターフェイスのみで継承の利点を享受できるでしょう。
また、「is-a関係」を表現するのであれば「抽象クラス」の継承がベストです。
スーパークラスは「汎化した概念」を表現するために定義するので、
わざわざ普通のクラスをスーパークラスにする必要はありません。
以上です!
さいごに
個人的には今回の記事ですっきりしました。
間違いや不足があれば教えていただけると嬉しいです。
ところで前回コメントで名前が挙がった「Go」の名前を初めて聞いたのが2,3年前なので、
かなり新しい言語だと思ってたんですが 2009年からあるんですね。
すげぇ人は先見の明があるもんなんだなぁ…
最新の言語のコンセプトや仕様を調べてみたいなぁ… と思った次第です。
長々とお付き合いいただきありがとうございました。
共通部分を親クラスに抽出し、差分を子クラスで修正する手法。アンチパターンとみられることが多い。 ↩