ここで、もう1つトレイトを重ねてみましょう。仕事を半分の時間で終わらせることができる、Agilerトレイトを定義してみます。
trait Agiler extends Engineer{ println("trait Agiler constructor") abstract override def work(time:Int) = { println("Agiler#work start") super.work(time / 2) println("Agiler#work end") } }
ProgrammerとAgilerの2つのトレイトをミックスインしてworkメソッドを実行してみましょう。
scala> val p = new Person with Programmer with Agiler class Engineer constructor class Person constructor trait Programmer constructor trait Agiler constructor p: Person with Programmer with Agiler = $anon$1@21e85538 scala> p.work(60) Agiler#work start Programmer#work start Person#work start 1つのタスクを15分で行います Person#work end Programmer#work end Agiler#work end
トレイトのコンストラクタは指定順序で呼ばれ、Agiler→Programmerの順にメソッドが呼ばれています。トレイトにおいて、その指定順序は重要です。先ほどはProgrammer、Agilerの順番でトレイトをミックスインしていましたが、これを逆にしてみました。
scala> val p = new Person with Agiler with Programmer class Engineer constructor class Person constructor trait Agiler constructor trait Programmer constructor p: Person with Agiler with Programmer = $anon$1@5f91e28c scala> p.work(60) Programmer#work start Agiler#work start Person#work start 1つのタスクを22分で行います Person#work end Agiler#work end Programmer#work end
コンストラクタの実行順序とトレイトが実行される順番が変わり、実行結果も変わっています。積み重ねたトレイトは常に指定した右から順番に実行されるわけではありませんが、だいたい指定した右から順番に実行されます。
先ほど、「トレイトを積み重ねたときの実行順序はだいたい右から実行される」と言いましたが、しっかりと実行される順序は決まっています。C++などの一般的な多重継承では、superによって呼び出されるメソッドはsuperをどこで呼び出しているかで決まりますが、Scalaのトレイトの場合「トレイトを線形化(linearization)した結果」によって決まります。
newを使用してクラスのインスタンスを作成したとき、Scalaは自身のクラス、継承されているクラスやトレイトすべてを1本の直線的順序に並べます。これを「線形化」と呼びます。この線形化されたクラス/トレイトのどこかでsuperを使用した場合、線形化によって並べられた順序でクラスやトレイトに属するメソッドが呼び出されるようになります。
こうして、すべてのメソッドやsuperが適切に呼び出された結果、積み重ね可能な変更が実現します。ポイントは、「線形化では、あるクラスはすべてのスーパークラス、ミックスインされているすべてのトレイトよりも前に配置される」ということです。superを呼び出すメソッドを記述したとき、そのメソッドはスーパークラスやミックスインされたトレイトの振る舞いを変更します。
では、サンプルを見てみましょう。サンプルではベースとなるEngineerクラスを基に、DesignerトレイトとArchitectトレイトをミックスインしているPersonクラスを使用します。
class Engineer trait Designer extends Engineer trait Programmer extends Engineer trait Architect extends Programmer class Person extends Engineer with Designer with Architect
クラスとトレイトの継承関係は図のようになっています。
では順を追って線形化していきましょう。Personの線形化順序は後ろから前に決まっていきます。そのため、線形化の最後はPersonのスーパークラスのEngineerになります。Engineerは明示的な継承やトレイトのミックスインをしていないので、デフォルトのAnyRefを継承します。AnyRefはAnyを継承しているので、Engineerの線形化は、以下です。
Engineer→AnyRef→Any
次に線形化されるのは、最初(Archtectの左側)にミックスインしたDesignerトレイトです。すでにEngineerからの線形化に含まれているクラスは省略されるので、線形化順序は、以下です。
Designer→Engineer→AnyRef→Any
そして、この前にArchitectの線形化が行われます。ここでも、すでに線形化に含まれているクラスは省略されます。
Architect→Programmer→Designer→Engineer→AnyRef→Any
最後に、線形化の先頭はPerson自身になります。
Person→Architect→Programmer→Designer→Engineer→AnyRef→Any
これでPersonの線形化ができました。それぞれの線形化順序を表に示します。
型 | 線形化順序 |
---|---|
Engineer | Engineer→AnyRef→Any |
Designer | Designer→Engineer→AnyRef→Any |
Programmer | Programmer→Engineer→AnyRef→Any |
Architect | Architect→Programmer→Engineer→AnyRef→Any |
Person | Person→Architect→Programmer→Designer→Engineer→AnyRef→Any |
表 サンプルで使用する型の線形化順序 |
これらのクラス、トレイトがsuperを使用してメソッドを呼び出した場合に実行されるのは、線形化順序の自身の右隣にある実装です。このように、ルールに従って線形化され、superで何が呼び出されるかは明確に決まっています。
なお、線形化の詳細な仕様についてはScalaの言語仕様書を参照してください。
今回はScalaの重要な機能であるトレイトを紹介しました。Javaのインターフェイスと似ている部分もありますが、実装を持つことができたり、複数トレイトのミックスインができたりと、幅広い使い方が可能です。
次回は、型のパラメータ化を紹介します。
中村修太(なかむら しゅうた)
クラスメソッド勤務の新しもの好きプログラマーです。昨年、東京から山口県に引っ越し、現在はノマドワーカーとして働いています。好きなJazzを聴きながらプログラミングするのが大好きです。
Copyright © ITmedia, Inc. All Rights Reserved.