LoginSignup
77
66

More than 5 years have passed since last update.

Go Patternsで学ぶGo

Last updated at Posted at 2018-12-03

おはようございます。@mnumaです。
Makuake Product Team Advent Calendar 2018 3日目の記事です。
Makuake で最近支援したのは あべ養鶏場の燻製卵です。

株式会社マクアケではマイクロサービス化に伴い、バックエンドやツールにGo言語を採用することが増えています。というわけで今回はGoに関する記事を書きます。

Go Patterns

image.png (473.2 kB)

Go PatternsはGoのデザインパターン、レシピ、イディオムとサンプルコード集です。今日はこの中から勉強になったものをいくつか紹介したいと思います。

Functional Options

ここで紹介されている Functional OptionsはGo言語で柔軟性の高いオプションやデフォルト値が指定できたりするものです。
サンプルで紹介されていたものは少し複雑ったので、今回はこのパターンを使ってHTTPリクエストのオプションを組み立てるようなものを作成してみました。

オプションとオプションを定義する関数を定義します。

type RequestOptions struct {
    page    int
    perPage int
    sort    string
}

// Option型を引数にとる関数型 `Option` を定義します
type Option func(request *RequestOptions)

func Page(p int) Option {
    return func(r *RequestOptions) {
        if r != nil {
            r.page = p
        }
    }
}

func PerPage(pp int) Option {
    return func(r *RequestOptions) {
        if r != nil {
            r.perPage = pp
        }
    }
}

func Sort(s string) Option {
    return func(r *RequestOptions) {
        if r != nil {
            r.sort = s
        }
    }
}

次にコンストラクタを作ります。この際初期値とするのがデフォルト値となります。

func NewRequest(opts ...Option) *RequestOptions {
    // デフォルト値
    r := &RequestOptions{page: 1, perPage: 30, sort: "desc"}
    for _, opt := range opts {
        opt(r)
    }
    return r
}

Usage

func main() {
    r := NewRequest()
    // `Pageを設定`
    r = NewRequest(Page(10))
    fmt.Println(r)
    // `Page`と`PerPage``Sort`を設定`
    r = NewRequest(Page(10), PerPage(2), Sort("asc"))
    fmt.Println(r)
}

実行コードはこちらです。
image.png (933 B) https://play.golang.org/p/1IwBBgA-AWd

Observer Pattern

次にObserver Patternです。こちらの記事などに詳しいですが、あるオブジェクトの状態変化を、観察者(Observer)に状態変化を通知する仕組みです。

サンプル通り、EventObserver Notifierを定義していきます。

type (
    Event struct {
        Data int64
    }

    Observer interface {
        OnNotify(Event)
    }

    Notifier interface {
        Register(Observer)
        Deregister(Observer)
        Notify(Event)
    }
)
type (
    eventObserver struct{
        id int
    }

    eventNotifier struct{
        observers map[Observer]struct{}
    }
)

func (o *eventObserver) OnNotify(e Event) {
    fmt.Printf("*** Observer %d received: %d\n", o.id, e.Data)
}

func (o *eventNotifier) Register(l Observer) {
    o.observers[l] = struct{}{}
}

func (o *eventNotifier) Deregister(l Observer) {
    delete(o.observers, l)
}

func (p *eventNotifier) Notify(e Event) {
    for o := range p.observers {
        o.OnNotify(e)
    }
}

Usage

func main() {
    n := eventNotifier{
        observers: map[Observer]struct{}{},
    }

    // オブザーバーを登録します
    n.Register(&eventObserver{id: 1})
    n.Register(&eventObserver{id: 2})

    stop := time.NewTimer(10 * time.Second).C
    tick := time.NewTicker(time.Second).C
    for {
        select {
        case <- stop:
            return
        case t := <-tick:
            n.Notify(Event{Data: t.UnixNano()})
        }
    }
}
  • 図で見るとこのような感じです。

image.png (114.7 kB)

image.png (933 B) https://play.golang.org/p/fkraXpBOY83

Semaphore Pattern

最後にchannelを使ったセマフォのパターンの実装です。
こちらの例では処理のタイムアウトを意識したセマフォの紹介がされています。

var (
    ErrNoTickets      = errors.New("semaphore: could not aquire semaphore")
    ErrIllegalRelease = errors.New("semaphore: can't release the semaphore without acquiring it first")
)

type Interface interface {
    Acquire() error
    Release() error
}

type implementation struct {
    sem     chan struct{}
    timeout time.Duration
}

func (s *implementation) Acquire() error {
    select {
    case s.sem <- struct{}{}:
        return nil
    case <-time.After(s.timeout):
        return ErrNoTickets
    }
}

func (s *implementation) Release() error {
    select {
    case _ = <-s.sem:
        return nil
    case <-time.After(s.timeout):
        return ErrIllegalRelease
    }
}

func New(tickets int, timeout time.Duration) Interface {
    return &implementation{
        sem:     make(chan struct{}, tickets),
        timeout: timeout,
    }
}

Usage

func main() {
    // 同時処理数10、タイムアウト6秒
    tickets, timeout := 10, 6*time.Second
    s := New(tickets, timeout)

    for i := 0; i <= 100; i++ {
        if err := s.Acquire(); err != nil {
            panic(err)
        }

        go func(i int) {
            // なにか重い処理
            something(i)

            if err := s.Release(); err != nil {
                panic(err)
            }
        }(i)
    }
}

func something(i int) {
    fmt.Println(i)
    time.Sleep(5 * time.Second)
}

image.png (933 B) https://play.golang.org/p/5gQX-8i3S9B

まとめ

以上Go Patternsに掲載されているものについて一部紹介させて頂きました。
Go Patternsの内容としては少し説明やコードが不十分なものもあり、歯抜けになっている箇所もありますが、いわゆるデザインパターン的な実装だけでない領域も網羅していて、基礎的な部分から一歩踏み込んだ資料として、ぼんやり眺めるのにも良いかなと思いました!

最後になりますが、株式会社マクアケでは一緒に働くメンバーを募集しております。💪
ご興味が有りましたらご連絡いただけると幸いです!
https://www.wantedly.com/projects/26807

Makuake Product Team Advent Calendar 2018
まだまだ続きますので、明日以降もよろしくお願いします!

参考

77
66
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
77
66