Go Patternsで学ぶGo

おはようございます。@mnumaです。
[Makuake Product Team Advent Calendar 2018](https://qiita.com/advent-calendar/2018/makuake) 3日目の記事です。
[Makuake](https://www.makuake.com/) で最近支援したのは [あべ養鶏場の燻製卵](https://www.makuake.com/project/abe-youkei/)です。

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

# Go Patterns

![image.png (473.2 kB)](https://img.esa.io/uploads/production/attachments/7424/2018/11/28/27416/68c15b09-e3de-47d3-8ecc-92a298161748.png)

[Go Patterns](http://tmrts.com/go-patterns/)はGoのデザインパターン、レシピ、イディオムとサンプルコード集です。今日はこの中から勉強になったものをいくつか紹介したいと思います。

###  [Functional Options](http://tmrts.com/go-patterns/idiom/functional-options.html)


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

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

```go
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
		}
	}
}
```

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

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

```

#### Usage

```go
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://img.esa.io/uploads/production/attachments/7424/2018/12/01/27416/edc617b7-cabd-4f39-b54a-becd0f14535f.png) https://play.golang.org/p/1IwBBgA-AWd

### [Observer Pattern](http://tmrts.com/go-patterns/behavioral/observer.html)


次に`Observer Pattern`です。[こちら](http://www.techscore.com/tech/DesignPattern/Observer.html/)の記事などに詳しいですが、あるオブジェクトの状態変化を、観察者(Observer)に状態変化を通知する仕組みです。

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

```go
type (
	Event struct {
		Data int64
	}

	Observer interface {
		OnNotify(Event)
	}

	Notifier interface {
		Register(Observer)
		Deregister(Observer)
		Notify(Event)
	}
)
```

```go
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

```go
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)](https://img.esa.io/uploads/production/attachments/7424/2018/12/01/27416/13681501-52b2-4d0d-bd33-1717a6cc67e5.png)


![image.png (933 B)](https://img.esa.io/uploads/production/attachments/7424/2018/12/01/27416/edc617b7-cabd-4f39-b54a-becd0f14535f.png)   https://play.golang.org/p/fkraXpBOY83


### [Semaphore Pattern](http://tmrts.com/go-patterns/synchronization/semaphore.html)

最後に`channel`を使った[セマフォ](https://ja.wikipedia.org/wiki/%E3%82%BB%E3%83%9E%E3%83%95%E3%82%A9)のパターンの実装です。
こちらの例では処理のタイムアウトを意識したセマフォの紹介がされています。

```go
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

```go
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://img.esa.io/uploads/production/attachments/7424/2018/12/01/27416/edc617b7-cabd-4f39-b54a-becd0f14535f.png)   https://play.golang.org/p/5gQX-8i3S9B



# まとめ

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

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

[Makuake Product Team Advent Calendar 2018](https://qiita.com/advent-calendar/2018/makuake)
まだまだ続きますので、明日以降もよろしくお願いします！

## 参考

- Observer パターン http://www.techscore.com/tech/DesignPattern/Observer.html/