🐻‍❄️

【Go】インターフェース理解をしたい!

2024/01/14に公開

はじめに

  • 私は下記の記事を読んでいました。

https://qiita.com/gold-kou/items/fdecea86fbe65e65160a

また、関数の引数をインターフェースで定義すると、そのインターフェースさえ満たしていればどんな構造体も引数として渡すことができます。
このテクニックは、汎用的な関数を定義するうえでかなり役立つテクニックです。

  • そこで上記の文が目に止まりました。
  • かなり役立つテクニックなら身につけないわけには行かない!と思いました。
  • しかし、Go初学者の身としては一見しただけでは、意味が分かりませんでした。
  • なので、今回は深く掘り下げて考えていきます。

まずは、わかりやすく

interfaceとは

  • インターフェースには、ルールの定義ができる
  • 例えば、下記のように
type ウェイター interface {
    料理を運ぶ()
}
  • Goでは「料理を運ぶ人」は「ウェイター」とみなす。※いわゆるダックタイピング

ダックタイピングの利点

  • マネージャーはウェイターだろうが誰だろうが「料理を運ベる人」に指示を出せる
  • つまり、「この仕事をするためには、これらの能力が必要だ」とルールを定義していて、それに従っている人(またはコード上の構造体)なら誰でもその仕事を任せることができる

httptestパッケージの例を見てみる

Handler インターフェースの定義の確認

http/server.go
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
  • ServeHTTPはHTTPリクエストを受け取り、適切なレスポンスを生成してクライアントに返すメソッド
  • これを実装している構造体はHandlerとみなすことにする

ServeMux(構造体)とメソッドの実装を見てみる

http/server.go
type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry // slice of entries sorted from longest to shortest.
	hosts bool       // whether any patterns contain hostnames
}
  • ServeMuxはURLルートとそれに対応するハンドラを管理する構造体。
http/server.go
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	if r.RequestURI == "*" {
		if r.ProtoAtLeast(1, 1) {
			w.Header().Set("Connection", "close")
		}
		w.WriteHeader(StatusBadRequest)
		return
	}
	h, _ := mux.Handler(r)
}
  • このServeMuxは ServeHTTP メソッドを実装しているため、http.Handler インターフェースを実装しているのでHandlerとして扱おうとなる(ダックタイピング)。
  • あくまでServeMuxは一例で、下記も該当する(Handlerとして扱おうとなる)
http/server.go
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	handler := sh.srv.Handler
	if handler == nil {
		handler = DefaultServeMux
	}
	if !sh.srv.DisableGeneralOptionsHandler && req.RequestURI == "*" && req.Method == "OPTIONS" {
		handler = globalOptionsHandler{}
	}

	handler.ServeHTTP(rw, req)
}

httptest.NewServer 関数の使用

http/httptest/server.go
func NewServer(handler http.Handler) *Server {
    ts := NewUnstartedServer(handler)
    ts.Start()
    return ts
}
  • httptest.NewServer 関数は http.Handler インターフェースを引数として受け取る。
  • つまり、 「Handlerとみなされているものなら何でも処理します」 という関数
  • この関数はテスト用HTTPサーバーを作成し、指定されたハンドラ(http.Handler インターフェースを実装したオブジェクト)でリクエストを処理する。

httptest.NewServeにServeMuxを渡す

  • ServeMux インスタンスは http.Handler インターフェースを実装しているため、httptest.NewServer 関数の引数として渡すことができる。
  • これにより、作成されたHTTPサーバーは ServeMux インスタンスが提供するルーティングロジックを使用してリクエストを処理する。

これで、冒頭の

関数の引数をインターフェースで定義すると、そのインターフェースさえ満たしていればどんな構造体も引数として渡すことができます。

が理解できる。

main.go
mux := http.NewServeMux()
mux.HandleFunc("/path", handlerFunction) // 例
testServer := httptest.NewServer(mux)

メリットについて

  • NewServerの 「Handlerとみなされているものなら何でも処理します」 という状態は 
    マネージャーの 「ウェイターだろうが誰だろうが「料理を運ベる人」に指示を出せる」 状態であると言える

最後に

  • 今回は関数に「関数の引数をインターフェースで定義し、そのインターフェースさえ満たしていればどんな構造体も引数として渡すことができる」 というテクニックについて実際のhttptestパッケージのソースを見ながら確認しました。
  • これはinterfaceの1つの側面です。
  • まだまだ、使い方があるようなので記事にしていきたいと思います。

Discussion