トレイトによるMix-in
Scalaには、トレイトというMix-Inのための仕組みが用意されています。トレイトは、実装を持つことができるインターフェイス、あるいは多重継承を許可する抽象クラスとも言えます。
さっそくトレイトの例を見てみましょう。リスト9では、JpDateFormatterとという日付を日本語に整形する機能を持つトレイトと、URLSourceLoaderというURLオブジェクトから文字列を読み出す機能を持つ、2つのトレイトを定義しています。
// 日付を日本語で整形する機能を持つトレイト
trait JpDateFormatter {
import java.text.SimpleDateFormat
// 日付を整形するメソッド
def formatDate( d:Date ) = {
val formatter = new SimpleDateFormat("yyyy年MM月dd日 HH時mm分ss秒")
formatter.format( d )
}
}
// URLから文字列を読み出す機能を持つトレイト
trait URLSourceLoader {
import scala.io.Source
val url:URL // 抽象メンバー。このトレイトをMix-Inされるクラスで実装する
// scala.io.Sourceオブジェクトを利用してurlから文字列で読み出す
def load = Source.fromURL( url ).getLines().toSeq
}
トレイトは、traitキーワードで定義します。objectと同様にコンストラクタは定義できませんが、メンバーを持つことが可能ですし、他のクラスやトレイトを継承させることもできます。JpDateFormatterトレイトは、formatDateという実装を持つメソッドを持っています。JpDateFormatterがMix-Inされる先のクラスでは、このformatDateメソッドが利用できるようになるわけです。
URLSourceLoaderトレイトは、java.net.URL型の抽象メンバーurlを持っており、scala.io.Sourceクラスを利用してurlからネットワーク経由で文字列を読み出す機能を持ちます。
URLSourceLoaderトレイトをMix-inするクラスでは、抽象メンバーurlを定義する必要があります。このように、メンバー名と型の宣言だけを行った場合は、抽象メンバーとして扱われます。フィールドだけでなくdefによる抽象メソッドも定義可能です。
では、Bookmarkクラスにこの2つのトレイトをMix-Inしてみましょう。トレイトは、extendsとwithでMix-Inします。リスト1で示したScalaのクラス宣言の定義を思い出してください。
class Bookmark private ( val title:String, val url:URL, val date:Date ) extends JpDateFormatter with URLSourceLoader
// Bookmarkコンパニオンオブジェクト
object Bookmark {
// ...省略
}
extendsキーワードでJpDateFormatterトレイトを指定しています。Bookmarkクラスはスーパークラスを持っていないので、最初のトレイトのMix-Inにはextendsキーワードを指定します。次に、withキーワードで、URLSourceLoaderトレイトを追加でMix-inしています。このほかにもトレイトをMix-Inさせたい場合は、続けて「with トレイト」とつなげていくことで複数のMix-Inが可能です。
URLSourceLoaderトレイトで定義されていた抽象メンバーurlは、Bookmarkクラスのコンストラクタで受け取ってvalで公開されているurlフィールドによって実装されています。
では、REPLで2つのトレイトを定義し、新しくトレイトをMix-InしたBookmarkクラスを再度定義して、動作確認しましょう。
scala> trait JpDateFormatter {
// ...省略
| }
defined trait JpDateFormatter
scala> trait URLSourceLoader {
// ...省略
| }
defined trait URLSourceLoader
scala> object m{
| class Bookmark private ( val title:String, val url:URL, val date:Date ) extends JpDateFormatter with URLSourceLoader
|
| // Bookmarkコンパニオンオブジェクト
| object Bookmark {
// ...省略
| }
defined module m
scala> val bookmark = m.Bookmark("今から始めるScala 前編","http://codezine.jp/article/detail/5193/" )
bookmark: m.Bookmark = m$Bookmark@6dbd984
scala> bookmark.formatDate( bookmark.date )
res2: java.lang.String = 2010年06月18日 16時54分11秒
scala> bookmark.load
res3: Seq[String] = List(<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">, <html lang="ja">, <head>, <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">, <meta http-equiv="Content-Style-Type" content="text/css">, <meta http-equiv="Content-Script-Type" content="text/javascript">, <title>今からでも遅くない これから始めるScala(前編)(1/7):CodeZine</title>, ...
Bookmarkクラスのインスタンスには、JpDateFormatterトレイトから継承したformatDateメソッドやURLSourceLoaderトレイトから継承したloadメソッドが利用できるようになっていることが分かると思います。
このように、トレイトを利用することで複数の実装を持つトレイトをクラスにMix-Inさせてさまざまな機能を追加できるようになります。
