azihsoyn's blog

技術のこととか釣りの事とか(書けたらいいなぁ)

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しか使ってない)、色々考えたところ

普通の英文っぽいメソッドにすればいいのではないかとひらめきました。

// 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のクライアントライブラリが似たような実装をしてました。

github.com

これは最初の部分だけがinterfaceなので厳密にはinterface chainではないですが。

なんかこのinterface chainはあんまり見たことないような気がするので新しいパターンとして流行ったら面白いなぁという紹介でした。(すでにあったら恥ずかしい////)

www.calhoun.io

ちょっと前ツイッターで議論されていたこの記事も思いついたきっかけのような気がします。


また、今回はメソッドチェーンにしましたが

timex.A(t1).GreaterThan.B(t2)

timex.X(t1).Between.Y(t2).And.Z(t3)

みたいな書き方もできますね。(XはBetweenフィールドを持ったstructを返す。Between フィールドはYメソッドを持つ。YメソッドはAndフィールドを持ったstructを返す...みたいなイメージ)

(この記事もmediumに英語で書きたい気持ち)