8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

はじめに

プログラミングしてると自分の頭からまま飛び出してくる「ここは継承つかえばとても良くなるのでは??」という勘違いを撲滅したい。
継承を使ってはいけないことの理解がふわっとしてるせいだと思うので、ここで言語化して整理する。

主に Why extends is evil を参考にしているので Java が下地。

継承がダメなところ

結合度が高くなる

実装の継承(extends)を使うと結合度が高くなり、実装の変更をするときに泣く。

結合度とは

プログラムが再利用や保守・テストしやすくするためコードが整理・分割されているかを示す尺度。
ちなみに 結合 とはあるモジュール(クラスやメソッドなどのプログラムの部品)が別のモジュールに依存していること。

結合度については低いほど望ましい。
プログラムの結合度が低いということはモジュールの独立性が高いということであり、

  • 再利用性や保守性が高い(変更に強い)
  • テストがしやすい

というメリットがある。
この結合度が低いことを 疎結合 という。

プログラムの疎結合を保つために重要なのはオブジェクトの実装を使用する側から完全に隠してしまうこと。
つまり カプセル化 が重要。

反対の例として、グローバル変数を必要とする実装を書くと簡単に結合度を高くできる。

Fragile Base Class Problem

問題は、実装の継承(extends)をすると派生クラス(サブクラス)は基底クラス(スーパークラス)と密接に結合することになり、結合度が非常に高くなる。
このせいで基底クラスを変更するとその影響が派生クラスに波及し、派生クラスのモジュールを壊す可能性がある。

変更による影響は基底クラスだけをテストしても確認できないため、すべての派生クラスについてもテストを行って確かめる必要がある。
さらには基底クラスまたは派生クラスを使用しているすべてのモジュールも壊れる可能性があるため、こちらもすべてテストする必要がでてくる。

必ずしも is-a 関係は成立しない

Cook 氏らによる論文 Inheritance is not subtyping で提唱され、広く知られている問題らしい(恥ずかしながら知らなかった)。

Java や C#などの言語では継承に subtyping が組み込まれた言語仕様になっているが、もともとは別々の概念であるため継承では is-a 関係が成立しないことがある。
(継承は実装の再利用を、subtypingis-a 関係 の成立を担う)

is-a 関係が成立しない継承(インターフェース含む)を行ってしまうと、その基底クラスを必要とするモジュールが壊れる可能性がある。
そしてどの派生クラスが悪さをしたのかコードを精査する作業が待っている。

逆にいえば、まず subtypingis-a 関係)を成立させることが継承を実装する上で重要。

is-a 関係とは

AB の一種である(A is a B)として表される関係。
クラス図でいうところの A は特化、 B は汎化。

プログラミングでは下記の subtyping であること。

subtyping(派生型、部分型、subtype) とは

あるデータの型と別の型との関係で置換可能性(リスコフの置換原則)が成り立つこと。
すなわち、型 S が型 Tsubtyping である場合、型 T のオブジェクトが要求される箇所はその代わりとして型 S のオブジェクトが使用可能であること。

もう少し具体的に言うと、基底クラスが必要な箇所に派生クラスを代入しても問題なく動作するなら subtyping といえる。

subtyping はポリモーフィズム(多態性、多相性)の1つである。

Java の生みの親いわく

he explained that the real problem wasn't classes per se, but rather implementation inheritance (the extends relationship). Interface inheritance (the implements relationship) is preferable.
(Why extends is evil より引用)

意訳: (James Gosling 氏は)実装の継承(extends)が問題であり、代わりにインターフェースの継承(implements) を使うことが望ましいと説明した。

まとめ

  1. プログラムは疎結合であることを目指すべきなのに、実装の継承(extends)は結合度を高くするからダメ
  2. インターフェースの継承(implements)においてもリスコフの置換原則を守るように実装する

また注意すべき事柄は以下。

  • 共通化してコード量を減らそうとがんばる
    • 差分プログラミングというアンチパターンに陥り Fragile Base Class Problem が発生しやすい
  • 副作用のある実装
    • subtyping が壊れやすい、テストしにくい
  • テストコードを書くのが難しい
    • 元のコードの結合度が高いと思われる

参考

8
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?