🔩 ねじき教室 Go と Web の教室

インターフェース

読了目安 約3分

「何ができるか」だけを定めるインターフェースと、io.Reader や io.Writer が Go 全体の共通言語になっている理由を学びます。

この章の目次

struct は「何を持っているか」を定める型でした。 この章で扱うのは「何ができるか」だけを定める型で、Go の標準ライブラリはこれを土台に組まれています。

「何ができるか」だけを定めた型

インターフェースは、メソッドの一覧だけを書いた型です。

Go
package main

import "fmt"

type Notifier interface {
	Notify() string
}

type User struct {
	Name string
}

func (u User) Notify() string {
	return u.Name + " さんに通知を送りました"
}

type Group struct {
	Name    string
	Members int
}

func (g Group) Notify() string {
	return fmt.Sprintf("グループ %s の %d 人に通知を送りました", g.Name, g.Members)
}

func send(n Notifier) {
	fmt.Println(n.Notify())
}

func main() {
	send(User{Name: "neziki"})
	send(Group{Name: "isucon-team", Members: 3})
}

send の引数は Notifier なので、「Notify() string を持つ」型なら User でも Group でも渡せます。

本番のコードでは、send を書いた後になって通知したい型が増えていきます。 インターフェースで受けていれば、send に一切手を入れずに追加できます。

暗黙の実装

このコードに、「User は Notifier を実装する」という宣言はありません。 Go には Java や C# の implements にあたるキーワードがなく、必要なメソッドをすべて持っていれば、その時点で自動的にインターフェースを満たします

だから後付けが利き、変更できない外部パッケージの型でも、自分の定義したインターフェースで受け取れます。

身近な例が fmt.Stringer です。String() string を持つ型を fmt.Println に渡すと、その戻り値が表示に使われます。章末の演習で試します。

io.Reader と io.Writer

Go 全体で共通言語になっているインターフェースが 2 つあります。 io.Reader は「バイト列を読み出せるもの」、io.Writer は「バイト列を書き込めるもの」を表し、定義はどちらもメソッド 1 つです。

Go
type Writer interface {
	Write(p []byte) (n int, err error)
}

データの出入りがあるもの(ファイル、ネットワーク接続、メモリ上のバッファ、圧縮ストリーム)は、標準ライブラリのほとんどがこの 2 つを実装しています。 だから「Writer を受け取る関数」は、書き込み先が何でも同じコードで動きます。

Go
package main

import (
	"bytes"
	"fmt"
	"os"
)

func main() {
	// 1 回目: 標準出力(画面)に書く
	fmt.Fprintln(os.Stdout, "画面へ")

	// 2 回目: メモリ上のバッファに書く
	var buf bytes.Buffer
	fmt.Fprintln(&buf, "バッファへ")
	fmt.Println("バッファの中身:", buf.String())
}

fmt.Fprintln の第 1 引数が io.Writer で、書き込み先が画面からメモリに変わっても呼び出す関数は同じです。

http.Handler もインターフェース

Part 3 で中心になる http.Handler も、メソッド 1 つのインターフェースです。

Go
type Handler interface {
	ServeHTTP(w http.ResponseWriter, r *http.Request)
}

この定義は、「ブラウザからの依頼を受けて返事を書けるものなら何でもサーバーに載せられる」という設計です。 使い方は ルーティングとハンドラ で扱います。

試してみよう

最初の Notifier の例を、2 段階で拡張してください。

  1. 新しい型 Bot(フィールドは好きに決めてください)を追加し、Notify() string を実装して send に渡す。send を 1 文字も変えずに動けば成功です。
  2. UserString() string メソッド(たとえば "User(neziki)" を返す)を追加し、mainfmt.Println(User{Name: "neziki"}) を足して、表示が変わることを確かめる。implements と書かずに User が fmt.Stringer を満たした瞬間です。