timexライブラリのアップデートとinterface chainパターンについて
2月と3月の振り返りまだしてないですがふとライブラリをアップデートするネタを思いついたので更新しました。
先日goのtimeを比較したりパースしたりするライブラリを作ったのですが、 azihsoyn.hatenablog.com
if (timex.Time{t1}).LessThan(t2) { // 今は timex.Time{t1}.LessThan(t2) ってできない return true }
Between以外はそんなにシンプルになってないなぁと思っていて(実際に業務コードでもBetweenしか使ってない)、色々考えたところ
普通の英文っぽいメソッドにすればいいのではないかとひらめきました。timexで
— ふそやん (@azihsoyn) 2018年4月12日
timex.A(a).GreaterThan().B(b)
もしくは
timex.A(a).GreaterThan.B(b)
みたいに書く方法思いついた
これなら独自の型をNewしなくても文脈が通るメソッドになる気がするhttps://t.co/KOmxuynPug
// t1 < t2 if timex.A(t1).LessThan().B(t2) { return true } // t1 <= now <= t2 if timex.X(now).Between().Y(t1).And().Z(t2) { return true }
こんな感じで使えます。
ちょっとだけ工夫してまして、
type Timex struct { op Operator t1 time.Time t2 time.Time } func A(t time.Time) *Timex { return &Timex{ t1 : t, } } func (x *Time) GreaterThan() *Time { op = GT return x } func (x *Time) LessThan() *Time { op = LT return x }
↑のようにただ普通にメソッドチェーンで実装してしまうと
timex.A(t).GreaterThan().LessThan().LessThan()......
のように意味のないチェーンでも使えてしまいます。補完も微妙になってしまいますし。
ので、
type Comparable interface { GreaterThan() ComparableResult GreaterEqual() ComparableResult LessThan() ComparableResult LessEqual() ComparableResult Equal() ComparableResult } type ComparableResult interface { B(t time.Time) bool } func A(t time.Time) Comparable { return &Timex{ t1 : t, } } func (x *Timex) GreaterThan() ComparableResult { x.op = GT return x }
のようにinterfaceでメソッドチェーンを定義することで想定した使い方でしか呼べないようにしました。
interfaceの定義をpublicにするか、structのメンバをpublicにするかは悩みましたが雑にえいやで実装しちゃいました。(interface名も大分雑です)
思いついてからどこかで見たことあるなーと思ってたんですが、業務でとてもお世話になっているredisのクライアントライブラリが似たような実装をしてました。
これは最初の部分だけがinterfaceなので厳密にはinterface chainではないですが。
なんかこのinterface chainはあんまり見たことないような気がするので新しいパターンとして流行ったら面白いなぁという紹介でした。(すでにあったら恥ずかしい////)
ちょっと前ツイッターで議論されていたこの記事も思いついたきっかけのような気がします。
また、今回はメソッドチェーンにしましたが
timex.A(t1).GreaterThan.B(t2) timex.X(t1).Between.Y(t2).And.Z(t3)
みたいな書き方もできますね。(XはBetweenフィールドを持ったstructを返す。Between フィールドはYメソッドを持つ。YメソッドはAndフィールドを持ったstructを返す...みたいなイメージ)
(この記事もmediumに英語で書きたい気持ち)