既存のクラス、または、third party のライブラリにメソッドを追加したい時がある。 Ruby と Scala で実現してみる。
例として、文字列のストリームから次の行の文字列を取得するメソッドを追加してみることにする。
Table of Contents
Open Table of Contents
Ruby は Open Class で
Ruby の場合だと、Open Class を使って容易に実現できる。標準ライブラリにある StringIO
で実施してみる。
>> sio = StringIO.new("line one\nline two")
#<StringIO:0x3cc670>
>> sio.next_line
NoMethodError: undefined method `next_line' for #<StringIO:0x3cc670>
from (irb):2
まだ実装していないので、当然ながらエラーとなる。
>> class StringIO
>> def next_line
>> line = gets
>> line.chomp if line
>> end
>> end
nil
>> sio.next_line
"line one"
>> sio.next_line
"line two"
>> sio.next_line
nil
OK だ。Ruby はプログラマに自由と責任を与えているので、上記のように容易に実現することができる。 ただ、注意点としては、既に存在するメソッドであっても容易に変更可能である点。
>> "Hello".to_s
"Hello"
>> class String
>> def to_s; "Overrided"; end
>> end
nil
>> "Hello".to_s
"Overrided"
>> "World".to_s
"Overrided"
意図する/しないに関わらず、容易にオーバーライドできてしまうので、既存のクラスを拡張する場合には注意と、オーバーライドした場合には、それなりの対応は必要となる。
Scala は trait を利用
では、Scala の場合。
よく参考にさせて頂いている以下の記事から引用させて頂く。
Ruby の場合と同様に、文字列のストリームから次の行の文字列を取得するメソッドを追加する。
ここでは、既存のクラスの java.io.Reader
に対して、nextLine
メソッドを追加する。
java.io.Reader
は、read
メソッドでバイト単位に文字を読み込むことができるのだが、行単位で読み込むメソッドの実装は無い。
これを行単位で行えるようにしよう、という試み。
(java.io.BufferedReader
に繋いでしまえばいいのだが、ここは例として。。)
java.io.Reader
に追加することで、その恩恵はそのサブクラスでも受けられるので、実装された nextLine
メソッドを java.io.StringReader
で利用する。
trait を使って実現する。
scala> trait Lines {
| this: java.io.Reader =>
| def nextLine: Option[String] = {
| val builder = new StringBuilder
| var next = read
|
| while (next != -1 && next.toByte.toChar != '\n') {
| builder += next.toByte.toChar
| next = read
| }
|
| if (builder.isEmpty) None else Some(builder.toString)
| }
| }
defined trait Lines
上記のように Lines
という trait を作成し、これを java.io.StringReader
に Mix-In する手法をとる。
scala> val reader = new java.io.StringReader("line one\nline two") with Lines
reader: java.io.StringReader with Lines = $anon$1@42dc5733
scala> reader.nextLine
res0: Option[String] = Some(line one)
scala> reader.nextLine
res1: Option[String] = Some(line two)
scala> reader.nextLine
res2: Option[String] = None
OK だ。
this: java.io.Reader =>
という表記は、self type (自分型) の定義で、クラス内で this
が使われるときに this
の型として想定されるものを定義している。
これにより、Lines trait で read
というメソッドを使っているが、これは、自分型 self
で指定した java.io.Reader
の read
メソッドが使用されている。
Implicit Convension (暗黙の型変換) を使っても実現できるのだろうが、trait を使用した方が今の自分にはわかり易くてよい。。
なお、Scala の場合は、既に実装済みの既存のメソッドをオーバーライドする場合には、明示的に override
修飾子を必要とするため、意図しないオーバーライドはコンパイル時に気付くことができる。