はじめに
scalaのimplicit classを使ったenrich my libraryパターンでZonedDateTimeを便利に使おうという話です。
環境
scala 2.10 以上(implicit classが使えるのが2.10から)
時刻情報を扱うのがめんどくさい
めんどくさくないですか?
java8からjava.timeパッケージが入って色々楽になったものの、まだめんどくさいです。
私が関わっているサービスでは、年月日でパーティションを切っているDBテーブルがあり、ZoneDateTimeからyyyyMMdd形式の数値(20190101とか20191231とか)を取り出したい場面が頻繁にあります。
そういうとき、以下のような処理が必要になります。
val now = ZonedDateTime.now() //とりあえず現在時刻
val formatter = DateTimeFormatter.ofPattern("yyyyMMdd")
val yyyyMMdd = now.format(formatter).toInt
DateTimeFormatterのインスタンスはスレッドセーフなので、どこかに定数として定義しておけばいいので、実質的には
now.format(formatter).toInt
の1行にできるんですが。。。。
now.yyyyMMdd.toInt
というふうにZonedDateTimeにメソッドが生えててほしくないですか?
生えててほしくないですか!!!?!?!?
それができるんです。
そう、Scalaならね。
implicit classでZonedDateTimeを便利に拡張しよう
implicit classというのは、既存クラスを拡張できる仕組みです。
詳しくはドワンゴ新卒研修資料のimplicitについての解説を参照するか、ググるかしてください。
以下のようなクラスを作ります
implicit class RichZonedDateTime(val self: ZonedDateTime) extends AnyVal {
private val yyyyMMddFormatter = DateTimeFormatter.ofPattern("yyyyMMdd")
def yyyyMMdd: Int = {
self.format(yyyyMMddFormatter).toInt
}
}
クラス名はなんでもいいのですが、拡張元のクラスの接頭辞にRichとつけるのが単純でいいかと思います。(ググって出てくるのは大体それ)
selfの型には拡張したいクラスを指定します。
上記のクラスがスコープ内にある状態だと、以下のようにZonedDateTimeのインスタンスから(ソースコード上は)直接yyyyMMddメソッドを叩けるようになります
val now = ZonedDateTime.now()
now.yyyyMMdd
yyyyMMddメソッドを叩いたタイミングで暗黙的にRichZonedDateTimeに変換されるという認識です。(あってるよね?)
今回のケースはシンプルですが、取り出しに複雑な処理が必要になるものをimplicit classのメソッドとして定義すると、便利に使えると思います。(月の最終日を取得するとか)
感想
デザインパターンの使い所あるあるパターン集みたいな本が欲しいです。
最近アンクルおじさんのClean Architecture本読んでるんですが(まだ半分)Interface(または各言語のそれに準ずる機能)の使い所の理解が深まりました。
JavaだとInterfaceの利用は避けては通れない感じがあるんですが、大学で最初に触ったときは、「なんで間にこんな何層も挟まるんだ?」と思いましたからね。