飛べない鳥問題
飛べない鳥問題がオブジェクト指向設計における継承の誤った例として度々挙げられる。
ハトやカラスは鳥であり、鳥は空を飛ぶのでfly()という振る舞いを持つ。
code:mmd
classDiagram
class 鳥 {
fly()
}
class ハト {
fly()
}
class カラス {
fly()
}
鳥 <|-- ハト
鳥 <|-- カラス
ところが、新たにペンギンを加えようとした時に問題が起こる。ペンギンは明らかに鳥であるにもかかわらず飛べない。これは、どう対処したら良いか?
code:mmd
classDiagram
class 鳥 {
fly()
}
class ハト {
fly()
}
class カラス {
fly()
}
class ペンギン {
fly() //飛べない!!
}
鳥 <|-- ハト
鳥 <|-- カラス
鳥 <|-- ペンギン
なぜ、このような問題が起こるのか? 最初から「鳥」に飛べないものがあることは予見できないのだから、問題が起きた時に修正すれば良い。一理あるが、そもそも「ある対象が、飛べるものであれば何であれ、fly()を実行できる」というのが達成したいことであり、「飛べるものであれば何であれ」が、見出した抽象概念なので、これに「鳥」という名付けを行うことが問題といえる。
抽象概念には「飛べる」という名付けをしておけば、ペンギンが出てきても何ら困ることはない。
code:mmd
classDiagram
class 飛べる {
fly()
}
class ハト {
fly()
}
class カラス {
fly()
}
class ペンギン {
}
飛べる <|.. ハト
飛べる <|.. カラス
だが、実用的なアプリケーションでは「鳥」という抽象概念が必要なシーンがある。データベースからデータを取ってきてオブジェクトを作ったり、データベースに保存したりする場合である。ことのきRDBのテーブルは、格納するデータの振る舞いには関心がないので、属性が同じであれば同一のテーブルに格納するように設計するのが通常である。したがって、ハトもカラスもペンギンも鳥テーブルに格納する。データベースとのマッピングを考えると、鳥クラスであれば鳥テーブルにマッピングできることが明確になるので、やはり鳥という抽象概念も欲しいところだろう。
ここまでの考察を踏まえ、ポリモーフィズムとデータベースとのマッピングを両立させるには以下のようなモデルが妥当であると考えられる。
code:mmd
classDiagram
class 飛べる {
fly()
}
<<interface>> 飛べる
class ハト {
fly()
}
class カラス {
fly()
}
class ペンギン {
}
class 鳥
飛べる <|.. ハト
飛べる <|.. カラス
鳥 <|-- ハト
鳥 <|-- カラス
鳥 <|-- ペンギン
つまり、抽象概念の見い出し方には、大きく分けて「振る舞いベース」「属性ベース」の2通りがある。
アプリケーション上でポリモーフィズムするためには、振る舞いのinterfaceをそれぞれ実装する。
抽象概念のインタフェースには「その振る舞いができる能力があること」を示す名付けをする。
鳥としてデータベースに対して保存・検索するためには、属性を引き継ぐためクラス継承を使う。(ただしポリモーフィズムをしないならば、ハトやカラスなどの子クラスを作る必要はない)
これらの区別を明確にしないと、後に設計上の混乱を招いたり、不要な「継承即悪」といった極端な主張に走る原因となる。