その後の飲み会の席で、こんな話題がでた。
「Scalaって菱形継承をどう解決してんの?」
Scalaを勉強中とはいえ、まだまだ勉強が足りない僕は、その質問にきちんと答えることができなかったので、ちょっと調べてみた。
■菱形継承問題とは
引用元:wikipedia 菱形継承問題
菱形継承問題とは、多重継承を認めているプログラム言語において、クラスが上記の図のような継承関係にあり、BとCがAのメソッドをそれぞれ異なる形でオーバーライドしていた場合、DのクラスはBかC、いずれのメソッドを継承するのか、という問題である。
JavaやC#などの言語では、そもそも多重継承を認めておらず、インターフェイスも最終的に具象化すべきクラス(上記図でいうとD)にて具体的な実装がなされるため、菱形継承は問題にならない。
■Scalaのトレイトではどうなる?
ScalaもJavaやC#と同様、言語仕様としてクラスの多重継承を認めていない。その一方でインターフェイスという概念も存在せず、代わりにトレイトという考え方が存在する。
トレイトとは、基本的にはインターフェイスと同様の考え方であるのだが、それ自体に具体的な実装を施すことができる。
(ちなみにインターフェイスでいうinplementsはトレイトではmixinと呼ぶ)
トレイトはインターフェイスと同様、複数のトレイトをクラスにmixinできるため、JavaやC#では起こり得なかった菱形継承問題が発生することになる。
■実際に動かしてみた
ではScalaで菱形継承を実装するとどうなるのか、実験してみよう。
package diamondProblem abstract class Animal { def howl() } trait DogTrait extends Animal { override def howl() = println("bow wow") } trait CatTrait extends Animal { override def howl() = println("mew") } class MyPet extends DogTrait with CatTrait { } object Driver { def main(args: Array[String]) { var pet = new MyPet pet.howl() } }前述した菱形継承の図と対応させると、AのクラスがAnimalクラスに該当する。これはhowl()メソッドを定義した抽象クラスである。
B、Cに当たるのは、Animalクラスを継承したトレイト、DogTraitとCatTrait。それぞれhowl()メソッドに具体的な実装を施している。
最後に、DogTraitとCatTraitを多重mixinしたMyPetクラス。
DriverでMyPetクラスのインスタンスを生成し、howl()メソッドを呼び出した。
ビルドしてみたところ、正常終了。実行したら以下の結果が出た。
% scala diamondProblem.Driver mew
CatTraitのメソッドを継承するようだ。
続いて、コードをこのように変えてみた。
package diamondProblem abstract class Animal { def howl() } trait DogTrait extends Animal { override def howl() = println("bow wow") } trait CatTrait extends Animal { override def howl() = println("mew") } class MyPet extends CatTrait with DogTrait { } object Driver { def main(args: Array[String]) { var pet = new MyPet pet.howl() } }
"class MyPet extends DogTrait with CatTrait"のExtends 〇〇 with △△を入れ替え、"class MyPet extends CatTrait with DogTrait"としてみる。
実行すると…。
% scala diamondProblem.Driver bow wow
結果が変わった!
Scalaで菱形継承を実装した場合、Extends 〇〇 with △△ で定義された継承関係のうち、右側に位置するトレイトから優先して継承される(右側にあるトレイトが左側のトレイトを上書きする)ようだ。
ちなみに、菱形継承でなく、単純に同名のメソッドを有するトレイトを多重mixinした場合はこうなった。
package diamondProblem trait DogTrait { def howl() = println("bow wow") } trait CatTrait { def howl() = println("mew") } class MyPet extends CatTrait with DogTrait { } object Driver { def main(args: Array[String]) { var pet = new MyPet pet.howl() } }
これをビルドすると…。
% scalac diamondProblem.scala diamondProblem.scala:11: error: overriding method howl in trait CatTrait of type ()Unit; method howl in trait DogTrait of type ()Unit needs `override' modifier class MyPet extends CatTrait with DogTrait { ^ one error found
howl()をオーバーライドしろ、というエラーになる。
"Programming in Scala" (いわゆる「コップ本」) だと 12.6 でこの話題をあつかっています。
返信削除> Kato Kazuyoshi さん。
返信削除コメントありがとうございます。
コップ本は未読でした。読んでみたいと思います。