golang が提供するインタフェースの中で代表的な物の使い方をまとめてみる。
io.Reader
type Reader interface {
Read(p []byte) (n int, err error)
}
ご存じ
io.Reader
。このシグネチャの Read を実装しておけば golang のありとあらゆる入力機能に対して自分のコードを提供する事が出来る。
例えば永遠に「おっぱい」と言い続ける Reader だと以下の様な実装になる。
package main
import (
"io"
"os"
)
var text = []rune("おっぱい")
type OppaiReader struct {
n int
}
func (r *OppaiReader) Read(p []byte) (int, error) {
in := len(p)
nw := 0
for i := 0; i < in; i++ {
cb := []byte(string(text[r.n%len(text)]))
if nw+len(cb) > in {
break
}
cbl := len(cb)
copy(p[nw:nw+cbl], cb)
nw += cbl
r.n++
}
return nw, nil
}
func main() {
io.Copy(os.Stdout, &OppaiReader{})
}
ただしこういうことをする場合は、RuneReader を使った方が綺麗かもしれない。
io.Writer
type Writer interface {
Write(p []byte) (n int, err error)
}
io.Reader
のペアとなるインタフェース。同じくこの関数を実装しておけば golang のあらゆる出力機能に対して自分のコードを提供出来る。
例えば出力される文字列を Pascal Case で出力する Writer だと以下のコードになる。
package main
import (
"fmt"
"io"
"os"
)
type pascalCaseWriter struct {
w io.Writer
last byte
}
func (w *pascalCaseWriter) Write(p []byte) (int, error) {
r := 0
var b[1] byte
for n, _ := range p {
b[0] = p[n]
switch w.last {
case ' ', '\t', '\r', '\n', 0:
if 'a' <= b[0] && b[0] <= 'z' {
b[0] -= 32
}
}
nw, err := w.w.Write(b[:])
if err != nil {
return r + nw, err
}
w.last = b[0]
}
return r, nil
}
func NewPascalCaseWriter(w io.Writer) *pascalCaseWriter {
return &pascalCaseWriter{w, 0}
}
func main() {
w := NewPascalCaseWriter(os.Stdout)
fmt.Fprintln(w, "hello world")
}
Close
メソッドを提供するインタフェースとなるが、実際には単品で使われる事はなく、Reader かつ Closer、Writer かつ Closer という使われ方になる。
io.Seeker
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
名前の通り、指定オフセット位置でシーク出来るインタフェースとなる。これを実装した代表的な物としては
os.File
がある。
io.ReadWriter
type ReadWriter interface {
Reader
Writer
}
Reader でありかつ Writer であるインタフェースで双方向通信を行う為のインタフェース。代表的な実装としては
net.Conn
がある。
io.WriteCloser
type WriteCloser interface {
Writer
Closer
}
こちらは
os.Create
で作成した
os.File
が実装するインタフェース。
io.ByteReader
type ByteReader interface {
ReadByte() (c byte, err error)
}
ある入力から1バイトずつ読み込む事が出来るインタフェース。tail 的な処理を書きたい場合に用いる。
その他、組み合わせで用いる事が出来る
io.ReadWriteCloser
io.ReadSeeker
io.WriteSeeker
io.ReadWriteSeeker
io.ByteScanner
io.ByteWriter
io.RuneReader
io.RuneScanner
がある。
compress/flate/inflate.Reader
type Reader interface {
io.Reader
io.ByteReader
}
compress 圧縮の Reader を提供する。圧縮されたファイル等をこれを用いて Read すると等価的に内容が読めるという物。
実際には
NewReader
を用いて使用する。
database/sql.Driver
type Driver interface {
Open(name string) (Conn, error)
}
データベースドライバ実装を表すインタフェース。新しいデータベースのバインディングをサポートする場合、まずこのインタフェース実装を書くことになる。
database/sql.Execer
type Execer interface {
Exec(query string, args []Value) (Result, error)
}
通常は
database/sql
では
Prepare
して
Exec
するのが普通の使い方だが、データベース接続が Execer を実装している場合は、いちいち
Prepare
等せずいきなり接続から実行できる事を明示する事が出来る。
ドライバの実装は必須ではない。
database/sql.Queryer
type Queryer interface {
Query(query string, args []Value) (Rows, error)
}
Execer と同様に、結果を得るクエリの場合
Prepare
して
Query
する必要があるが、データベース接続から直接 Query 出来るドライバである事を明示する事が出来る。
ドライバの実装は必須ではない。
database/sql.ColumnConverter
type ColumnConverter interface {
ColumnConverter(idx int) ValueConverter
}
golang の
database/sql
は自動で型変換を行ってはくれるが、ある特殊な型に対して変換を行いたい場合にはこのインタフェースを実装しする。このインタフェースを実装する場合、ValueConverter も実装する必要がある。
database/sql.ValueConverter
type ValueConverter interface {
ConvertValue(v interface{}) (Value, error)
}
各型に対する変換処理を提供するインタフェース。例えば bool 型から各型への変換を提供する boolType では以下の実装になっている。
func (boolType) ConvertValue(src interface{}) (Value, error) {
switch s := src.(type) {
case bool:
return s, nil
case string:
b, err := strconv.ParseBool(s)
if err != nil {
return nil, fmt.Errorf("sql/driver: couldn't convert %q into type bool", s)
}
return b, nil
case []byte:
b, err := strconv.ParseBool(string(s))
if err != nil {
return nil, fmt.Errorf("sql/driver: couldn't convert %q into type bool", s)
}
return b, nil
}
sv := reflect.ValueOf(src)
switch sv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
iv := sv.Int()
if iv == 1 || iv == 0 {
return iv == 1, nil
}
return nil, fmt.Errorf("sql/driver: couldn't convert %d into type bool", iv)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
uv := sv.Uint()
if uv == 1 || uv == 0 {
return uv == 1, nil
}
return nil, fmt.Errorf("sql/driver: couldn't convert %d into type bool", uv)
}
return nil, fmt.Errorf("sql/driver: couldn't convert %v (%T) into type bool", src, src)
}
ドライバの実装は必須ではない。
database/sql.Valuer
type Valuer interface {
Value() (Value, error)
}
golang のデータベースドライバを実装するパッケージに対して golang 側から値を要求する際に用いられるインタフェース。
ドライバの実装は必須ではない。
database/sql.Scanner
type Scanner interface {
Scan(src interface{}) error
}
rows.Scan
に対して自前の実装を入れたい場合にインプリメントする。
ドライバの実装は必須ではない。
binary.ByteOrder
type ByteOrder interface {
Uint16([]byte) uint16
Uint32([]byte) uint32
Uint64([]byte) uint64
PutUint16([]byte, uint16)
PutUint32([]byte, uint32)
PutUint64([]byte, uint64)
String() string
}
binary.BigEndian
と
binary.LittleEndian
を実装に持つインタフェース。ユーザがさらに実装を追加する事はおそらく無い、BigEndian と LittleEndian どちらでやってくるか分からない処理に対して等価的に処理を行いたい場合には、このインタフェースを引数に取って扱う。
encoding.BinaryMarshaler
type BinaryMarshaler interface {
MarshalBinary() (data []byte, err error)
}
あるインスタンスがバイナリ列へシリアライズ可能かどうかを示すインタフェース。
binary.BinaryUnmarshaler
type BinaryUnmarshaler interface {
UnmarshalBinary(data []byte) error
}
あるインスタンスがバイナリ列からデシリアライズ可能かどうかを示すインタフェース。
encoding.TextMarshaler
encoding.TextUnmarshaler
も同様に、文字列に対する交換インタフェースを提供する。
encoding/gob.GobEncoder
type GobEncoder interface {
GobEncode() ([]byte, error)
}
golang には標準で Gob というシリアライザブルフォーマットに対するインタフェースが提供されているが、インスタンスに対して Gob フォーマットへの交換が可能かどうかを明示出来る。
encoding/gob.GobDecoder
type GobDecoder interface {
GobDecode([]byte) error
}
同様に Gob からある型への交換が可能かどうかを明示出来る。
encoding/json.Unmarshaler
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}
JSON を示すバイト列からある型への交換が可能である事を示す。
encoding/json.Marshaler
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
Unmarshaler とは逆にバイト列からある型への交換が可能である事を示す。
Marshaler と Unmarshaler の例を以下に示す。
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"log"
)
type User struct {
Name string
Password string
}
func (user *User) UnmarshalJSON(p []byte) error {
var v map[string]interface{}
err := json.Unmarshal(p, &v)
if err != nil {
return err
}
for _, f := range []string{"Name", "name"} {
if vv, ok := v[f]; ok {
user.Name = fmt.Sprint(vv)
break
}
}
for _, f := range []string{"Password", "password", "PassWord", "passWord"} {
if vv, ok := v[f]; ok {
b, err := base64.StdEncoding.DecodeString(fmt.Sprint(vv))
if err != nil {
return nil
}
user.Password = string(b)
break
}
}
return nil
}
func (user *User) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`{"Name": %q: "Password": %q}`,
user.Name, base64.StdEncoding.EncodeToString([]byte(user.Password)))), nil
}
func main() {
p := &User{"ウルトラマン", "henshin"}
b, err := p.MarshalJSON()
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
err = p.UnmarshalJSON([]byte(`
{
"Name": "仮面ライダー",
"Password": "aGVuc2hpbg=="
}
`))
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s : %s\n", p.Name, p.Password)
}
encoding.xml.Unmarshaler
encoding.xml.Marshaler
も json と同様。
flag.Getter
type Getter interface {
Value
Get() interface{}
}
golang の flag パッケージを用いて、プログラム引数から独自の型へ変換を行いたい場合に使う。
fmt.Formatter
type State interface {
Write(b []byte) (ret int, err error)
Width() (wid int, ok bool)
Precision() (prec int, ok bool)
Flag(c int) bool
}
fmt.Printf
と同様の処理を自前実装したい場合に使う。
fmt.Stringer
type Stringer interface {
String() string
}
実はこのインタフェースが一番使われる事が多い。デバッグではよく
fmt.Println
を使う事が多いと思うが、これを実装しておくと独自の型が
fmt.Println
等に渡った時にどの様に表示されるかを実装する事が出来る。
package main
import (
"fmt"
)
type Oppai bool
func (o Oppai) String() string {
if o {
return "(・)(・)"
}
return "(◎)(◎)"
}
func main() {
var o Oppai
o = true
fmt.Println(o)
o = false
fmt.Println(o)
}
fmt.GoStringer
は同様のインタフェースではあるが、実際には内部処理のみで使われている。
fmt.Scanner
type Scanner interface {
Scan(state ScanState, verb rune) error
}
fmt.Scan
と同様の処理を自前実装したい場合に使う。
image/draw.Quantizer
type Quantizer interface {
Quantize(p color.Palette, m image.Image) color.Palette
}
独自のカラーパレットを実装したい場合に使用する。gif は 256 色しか出力出来ないのでこれを使って減色処理を行っている。
Drawer
はこのパレットを用いて描画を行う際に実装する。
image/jpeg.Reader
type Reader interface {
io.Reader
ReadByte() (c byte, err error)
}
jpeg を自前でデコードしたい場合に実装する。
net/http.RoundTripper
type RoundTripper interface {
RoundTrip(*Request) (*Response, error)
}
http のトランスポート層を抽象化する為に用いられるインタフェース。
デフォルトのタイムアウトやダイアラ、未知のプロキシに対応する場合はこれを実装する事になる。golang の
http.Client
は
file://
な URL でも Get する事が出来るが、これは内部でスキーマを判定して RoundTripper を切り替える事で実現している。
net/http.Handler
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
ご存じ
http.Handler
。これを実装しておくと、とりあえず golang 標準のウェブサーバに対するリクエストを処理出来る。
例えば
go-uwsgi はこのインタフェースを実装する事で uWSGI インタフェースを実現している。
net/http.ResponseWriter
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(int)
}
golang 標準の Web サーバ機能を使う場合にレスポンスを書き込むインタフェース。実際にはこれをユーザ側が実装する事はまずない。主にテスト結果を得る為に用いられる。
net/http.Flusher
内部処理やテストで用いられる。
net/http.Hijacker
type Hijacker interface {
Hijack() (net.Conn, *bufio.ReadWriter, error)
}
golang の http 通信は大部分が隠ぺいされている。しかし自前で接続を切ってしまったり、https の connect メソッドを実現する場合には都合が悪いインタフェースだ。そこで
req.Hijack()
を呼び出し、http 以外の通信を行う事が出来る。
websocket パッケージはこれを使って http ネゴシエーションを実現している。
net/http.CloseNotifier
type CloseNotifier interface {
CloseNotify() <-chan bool
}
ResponseWriter
を自前実装した場合、クライアントから接続を切られた事を検知する為に実装するインタフェース。
net.Listener
type Listener interface {
Accept() (c Conn, err error)
Close() error
Addr() Addr
}
自前実装をソケットとして扱わせる為に実装するインタフェース。上記で紹介した go-uwsgi でも自前で Accept を実装する事で http サーバとの間の通信層で uWSGI を喋っている。
sync.Locker
type Locker interface {
Lock()
Unlock()
}
sync.Mutex
が実装しているが、自前でもロックインタフェースを実装したい場合にインプリメントする。
実装するのは自由だが、インタフェースが取り決められていないと発散するのでそれを定義しているといった所だろうか。
以上、golang の標準パッケージに含まれる代表的なインタフェースを紹介してみた。
golang のコードがエレガントになるかどうかの半分は、このインタフェースにかかっている。ぜひ極めましょう。