Scalaのトレイトでプログラマをミックスインしてやんよスケーラブルで関数型でオブジェクト指向なScala入門(7)(2/3 ページ)

» 2012年06月20日 00時00分 公開
[中村修太クラスメソッド株式会社]

別のトレイトで両方とも同じシグネチャ(名前)のメソッドを実装していた場合

 意図せず複数実装したトレイトのシグネチャ(名前)が同じになってしまうこともあるかもしれません。そんなときには、どのような挙動になるでしょうか。下記のような2つのトレイトを用意して確認してみましょう。

trait Programmer {
    def write = println("コードを書きます")
}
 
trait Writer {
    def write = println("記事を書きます")
}

 どちらも引数、戻り値なしの「write」というメソッドを持っているトレイトです。この2つのトレイトをミックスインした場合はどうなるでしょうか? Personクラスを新しく定義し、ProgrammerとWriterをミックスインしてみましょう。

 scala> class Person extends Programmer with Writer
 <console>:9: error: overriding method write in trait Programmer of type => Unit;
  method write in trait Writer of type => Unit needs `override' modifier
        class Person extends Programmer with Writer

 エラーになってしまいました。同じシグネチャのメソッドを持つトレイトを複数ミックスインしたクラスでは、そのメソッドを必ずオーバーライドしなければいけません。

class Person extends Programmer with Writer {
     override def write = println("ドキュメントを書きます")
}

 writeメソッドをオーバーライドすればエラーもなくなり、実行できます。

 scala> val p = new Person
 p: Person = Person@41d4702d
 scala> p.write
 ドキュメントを書きます

トレイトで「super」を使用するには

 トレイトの場合でもクラスの継承のときと同じように、「super」キーワードを使えばミックスインしたトレイトのメソッドを呼び出せます。

 では、Personクラスのオーバーライドしたwrite内でsuperを使用してミックスインしたトレイトのwriteを呼び出すと、どうなるでしょうか。

class Person extends Programmer with Writer {
     override def write = super.write
}

 この場合呼び出されるメソッドは、一番右にあるwithで指定されたトレイト(この場合「Writer」)のメソッドが呼び出されます。

 scala> val p = new Person
 p: Person = Person@6e0d2a4e
 scala> p.write
 記事を書きます

 しかし、一番右に指定したトレイトのメソッドではなく、任意のトレイトのメソッドを呼び出したい場合もあると思います。そのような場合、以下の形式でメソッドを呼び出します。

super[トレイト名].メソッド

 Programmerトレイトのwriteを呼び出してみましょう。

class Person extends Programmer with Writer {
    override def write = super[Programmer].write
}

 Programmerのwriteメソッドが呼ばれています。

 scala> val p = new Person
 p: Person = Person@7eb79ed4
 scala> p.write
 コードを書きます

トレイト単体での使用

 いままではPersonクラスを使用してインスタンス化していましたが、トレイト単体でもインスタンス化できます

 下記の例では、Programmerトレイトを指定してインスタンス化しています。

 scala> val p = new Programmer{}
 p: java.lang.Object with Programmer = $anon$1@1b704fb0
 
 scala> p.write
 コードを書きます

 トレイト名の後ろの「{}」は必須です。これは、Programmerトレイトをミックスインした無名クラスのインスタンスを作成することを意味しています。もし、Programmerトレイトのwriteが抽象メソッドだった場合、下記のようにインスタンス化のタイミングで実装できます。

 trait Programmer {
   def write
 }
 
 scala> val p = new Programmer { def write = println("コーディングします") }

 また、複数のトレイトをミックスインしたインスタンスも作成可能です(Javaでは、複数のインターフェイスを実装した無名クラスのインスタンスを作成できません)。

 trait T1 {
  def t1 = println("t1")
 }
 
 trait T2 {
  def t2 = println("t2")
 }
 
 scala> val t = new T1 with T2 //複数のトレイトをミックスインしてインスタンス化
 t: java.lang.Object with T1 with T2 = $anon$1@c25f12d

 T1トレイトとT2トレイトをミックスインして無名クラスでインスタンス化しています。このように、トレイトはJavaのインターフェイス以上にいろいろな使い方が可能です。

積み重ね可能なトレイト

 トレイトはいくつも積み重ねることができ、それによってメソッドの挙動を変更できます。

 サンプルで使用するクラスとトレイトを定義しましょう。まずはすべてのクラスのベースとなる、Engineer抽象クラスを定義します。これはwork抽象メソッドを定義しています。

abstract class Engineer {
    println("class Engineer constructor")
 
    def work(time:Int)
}

 次にEngineerを継承したPersonを定義します。workクラスを定義しますが、これは渡された時間どおりにタスクを行います。

class Person extends Engineer{
    println("class Person constructor")
 
    def work(time:Int) = {
        println("Person#work start")
        println("1つのタスクを" + time + "分で行います")
        println("Person#work end")
    }
}

 Personクラスをインスタンス化してworkメソッドを実行してみましょう。

 scala> val p = new Person
 class Engineer constructor
 class Person constructor
 p: Person = Person@1412504b
 
 scala> p.work(60)
 Person#work start
 1つのタスクを60分で行います
 Person#work end

 workメソッドに渡した引数通りにtimeが表示されます。

「abstract override」キーワード

 次に、Programmerトレイトを定義します。Programmerのworkメソッドは、渡された時間より15分短縮してタスクを行えます(15分以下のタスクについては考えないでください)。

trait Programmer extends Engineer {
    println("trait Programmer constructor")
 
    abstract override def work(time:Int) = {
        println("Programmer#work start")
        super.work(time - 15)
        println("Programmer#work end")
    }
}

 このトレイトをよく見てください。Engineer抽象クラスをスーパークラスに指定しています。これは、「Programmerトレイトをミックスインできるのは、Engineerを継承するクラスだけである」ということを示しています。

 そして、もう1つの特徴は「abstract宣言されたメソッドの中でsuperの呼び出しを行っている」ということです。さて、このsuperは何を指しているのでしょうか? Engineerを示しているようにも見えますが、Engineerのworkは抽象メソッドです。この時点ではsuper.workは具体的な定義に束縛されていません。

 トレイト内でsuperを使用した呼び出しを行った場合、メソッドの具体的な定義がしてあるクラスやトレイトの後でミックスインされるのであれば正しく動きます。このトレイトがミックスインされたときにsuper.workの定義内容が動的に束縛されます。

 このような処理をやっていることをコンパイラに教えるために「abstract override」というキーワードが必要です。この修飾子はトレイトで記述することはできますが、クラスで記述することはできません。

 では、ProgrammerトレイトをミックスインしてPersonクラスをインスタンス化してみましょう。

 scala> val p = new Person with Programmer
 class Engineer constructor
 class Person constructor
 trait Programmer constructor
 p: Person with Programmer = $anon$1@4318dcec
 
 scala> p.work(60)
 Programmer#work start
 Person#work start
 1つのタスクを45分で行います
 Person#work end
 Programmer#work end

 workメソッドを呼ぶと、Programmerトレイトのworkメソッドが呼ばれ、その後にPersonクラスのworkが実行されていることが分かります。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。