多重継承の問題点
オブジェクト指向言語を幾つか触ってからJavaを触っているとinterfaceの存在が気になり始めた。
要は多重継承を回避するためにinterfaceを採用したという話は知ってるけど
そもそも多重継承のデメリットを理解していないから調べてみた。
名前衝突の問題
public class FAX{ public void print(){ //FAXを印刷する実装 } } public class Printer{ public void print() { //用紙を印刷する実装 } }
実装は面倒なのでしませんが、コメントの機能を実装したものと仮定しておく
早速FAXとPrinterを実装したNewPrinterクラスを実装する
FAXも印刷できるPrinterを実装するイメージ
ところが…
両方ともメソッド名が print のため、NewPrintクラス側ではどちらのメソッドを継承するのかが判断できない問題が発生してしまう
これが名前衝突の問題。
ダイアモンド継承の問題
- Baseクラスに「Aと出力するメソッド」print()メソッドを定義する。
- Baseクラス をchild_Aクラスとchild_Bクラスが継承する
- child_Bでprint()メソッドを「Bと出力するメソッド」にオーバーライドする
- child_Aとchild_Bを継承するchild_Cを定義する
こんな実装をしたとして....
このChild_Cでprint()メソッドを使用すると何が出力されるのか…というのがダイアモンド継承問題。
一応答えとしては
- Child_Aに問い合わせる
- Child_Aはprintメソッドを定義していないのでBaseクラスに問い合わせる
- Aが返ってくる
という結果になる。
なぜChild_Bが呼ばれないのか?
それには深さ優先度 という規約が関わっているらしい
深さ優先度とは先に継承したクラスを優先するという規約で、
今回の場合クラス優先度は Child_A > Child_Bになるため
結果としてChild_Bは呼び出されなかった...ということらしい
このダイアモンド継承の問題は片方が呼び出だされないところにあって、
本来実装したい機能を多重継承で実装する際に
「常にどちらが優先されるクラスなのか」を意識しながらコーディングしていくことになり
バグの度に無理矢理実装を追加し続けてクラスの関連性が把握できなくなり
最終的にはクラスがゲシュタルト崩壊してサヨウナラする原因になるとか...恐ろしい
このように多重継承は問題を抱えていますが、その考え方自体はシンプルでいいと思う
ただ、多重継承に限った事ではなく、継承にも言える事で、
継承という方法は既存コードを使いまわせるため便利だけど、
過剰な継承は誰も配線を把握できない爆弾を製造することになるので
過剰継承は控えた方がいいという認識で間違いはない
ちなみに多重継承を採用しているのはC++。
JAVAは冒頭で言ったように禁止(interfaceで多重継承的な実装を実現している)
RubyはMix-inで一応多重継承を採用しているみたい