会社のコードレビューで、同僚のコードが少しクラス設計が微妙だった(委譲で十分なのに継承でわかりづらくしていた)ことを指摘したことがあります。「こういう場合は委譲ではなく継承を使うべき」という指針として「リスコフの置換原則」や「is-a関係」を説明したかったのですが、
型のオブジェクト に関して真となる属性を とする。このとき が の派生型であれば、 型のオブジェクト について が真となる。
いろいろな記事を説明してもこんな説明ばかりだったのでうまく説明できませんでした😅
また、いくつかの記事で気になる記述を見つけたので、今後調べる際のためにメモしておきます。「実装の継承」と「インターフェイスの継承(おそらくRustのtraitやHaskellの型クラスのような話だと思いますが)」などもワードも調べてみます。
もう一回言いますけど便乗ステマとか冗談抜きに「inheritance is not subtyping」「継承とis-a関係を混同してはいけない」http://t.co/l0T4pJIqaB で、それはオブジェクト指向(の継承)自体の本質的難点です!
— S (ツイートはスレッド全体をご確認ください) (@esumii) February 2, 2015
何となく風の息づかいを感じるので念のため、継承でリスコフの置換原則を守ろうとしたら、少なくともisEqualToみたくselftypeを引数とするメソッドは禁止するしかないので、「継承でリスコフの置換原則」はそもそも制限つきでしか成り立たないです
— S (ツイートはスレッド全体をご確認ください) (@esumii) February 2, 2015
Liskovの置換原則が話題のようなので、つ http://t.co/015wJanFG5 いや、置換原則とか一言で説明されるけどもっと面白い話なんだよと言いたいのでこの論文ことあるごとに話題に出す
— Kota Mizushima (on a diet) (@kmizu) February 2, 2015
何か間違ってたらコメントなどください。
「inheritance is not subtyping」問題
この現象は,is_equal_toのようなバイナリメソッド(自分と同じ型のオブジェクトを引数として受け取るメソッド)が原因で,いわゆる「inheritance is not subtyping」問題として広く知られている(http://portal.acm.org/citation.cfm?id=96721&dl=ACM&coll=portal)。このような問題があるので,「インタフェースの継承は必要だが,実装の継承は有害である」という議論すらあるぐらいだ。
より正確には,「(実装の)継承とis-a関係を混同してはいけない」というべきだろう。「inheritance is not subtyping」問題からもわかるように,継承したからといって必ずしもis-a関係が成り立つとは限らないからだ。逆に,先のprintableの例からもわかるように,継承しなければis-a関係が成立しない,というわけでもない。
この記事をPythonで読み替えた説明の記事がありました。
実は友人とSlackで会話した時、以下のようなやり取りがあったのですが、実際にOCamlだと構造的部分型の機能でこれをカバーしてるそうです。
彼: 禁止しなくても守れそうだけどな。isEqualToの実装次第じゃない?
私: 親クラスで isEqualTo が実装されていた場合、 子クラス.isEqualTo(親クラス) でオーバーライドして実装するときにややこしそう
彼: isEqualTo の中身次第じゃない? 「あるアトリビュートが同じだったら同じ」というメソッドだったら問題ないと思う。
Python記事からの引用
面白いのは, 継承しなくても, is-aの関係が成立してしまうケースが存在することで, 次のFruitとOarngeクラスの例が考えられます.
厳密な意味(?)でis-a関係が成立しているわけではないですが, equalsによる比較ができてしまい, 一種の部分型(is-a関係)のような振る舞いが可能になります. OCamlなどでは, この辺を型推論により自動的に型付けしてくれるようで, このような型を構造的部分型(structual subtyping)と呼ぶようです(前述のITProの記事より).
少なくとも、「equalsのように自分自身の型の引数をもつメソッドを継承する際、is-a関係(subtypingの関係)が壊れてエラーが出る場合がある」ということは正しそうです。
「実装の継承」と「インタフェースの継承」について
一般に,実装の継承と(is-a関係が成立するという意味での)インタフェースの継承は異なることに気を付けたい。
@kmizu あ、はい、適当に省略しちゃっててすみません。そういうインターフェースの継承っぽいのがHaskellの型クラスだと思います(たぶん)
— S (ツイートはスレッド全体をご確認ください) (@esumii) February 2, 2015
このあたり、Haskellの型クラスやRustのtrait(他言語の類似機能)と絡めた話が読んでみたいです。
Pythonの世界に落とし込めば、「通常の継承は禁止して、abcモジュールを使ったMixInのみ許可するほうがいい」と主張できそうな気もします。