トレイト

聴きなれない単語が出てきた。トレイトというのは、Scala のオブジェクト指向プログラミングにおけるモジュール化の中心的な概念らしい。トレイトの特徴は次の3つだ。

  • 1つのクラスやトレイトに複数のトレイトを継承(ミックスイン)できる
  • 直接インスタンス化できない
  • クラスパラメータ(コンストラクタの引数)をとることができない

とりあえずは Ruby のモジュールのようなものだと理解した。

トレイトの定義

定義構文はシングルトンオブジェクトの定義に似ていて、object キーワードの代わりに trait キーワードを使う。

scala> trait Hello {
     |     val mes: String = "Hello, World!"
     |     def hello(): Unit = println(mes)
     | }
defined trait Hello

ただし、そのまま使うことはできない。

scala> Hello.hello
<console>:12: error: not found: value Hello
       Hello.hello
       ^

1つのクラスやトレイトに複数のトレイトを継承(ミックスイン)できる

トレイトの場合は継承というよりミックスインということが多いらしい。extends キーワードを使う継承は1つのクラスしかすることができないけど、with キーワードを使うミックスインは複数できる。

scala> trait TraitA
defined trait TraitA

scala> trait TraitB
defined trait TraitB

scala> class ClassA
defined class ClassA

scala> class ClassB
defined class ClassB

scala> class ClassC extends ClassA with TraitA with TraitB
defined class ClassC

ここで ClassC は ClassA を継承し、TraitA と TraitB をミックスインしている。一方、with キーワードを使っても ClassA と ClassB を継承することはできない。

scala> class ClassD extends CrassA with ClassB
<console>:12: error: not found: type CrassA
       class ClassD extends CrassA with ClassB
                            ^

直接インスタンス化できない

scala> val hello = new Hello
<console>:12: error: trait Hello is abstract; cannot be instantiated
       val hello = new Hello
                   ^

クラスパラメータ(コンストラクタ引数)をとることができない

直接インスタンス化できないんだからコンストラクタ引数をとれないのは当然だけど、じゃあ、定義時に決まらないフィールドはどうすればいいかというと、継承先のクラスで上書きしてやればいい。

scala> trait TraitA {
     |     val name: String
     |     def printName(): Unit = println(name)
     | }
defined trait TraitA

scala> class ClassA(val name: String) extends TraitA
defined class ClassA

scala> val a = new ClassA("Bill")
a: ClassA = ClassA@21bc3814

scala> a.printName
Bill

TraitA の name フィールドは実装のない抽象フィールドだけど、trait のまえに abstract キーワードをつけなくて構わない。フィールドの実装は ClassA で与えられている。ここで気が付いたけど、1つだけ継承するときはそれがトレイトでも extends キーワードを使う。

さてここまででトレイトの基本を見てきた。つぎはトレイトの機能を見ることにしよう。というところでいったんここまで。