golang が提供するインタフェースの中で代表的な物の使い方をまとめてみる。
例えば出力される文字列を Pascal Case で出力する Writer だと以下のコードになる。
その他、組み合わせで用いる事が出来る
実際には
ドライバの実装は必須ではない。
ドライバの実装は必須ではない。
ドライバの実装は必須ではない。
ドライバの実装は必須ではない。
Marshaler と Unmarshaler の例を以下に示す。
デフォルトのタイムアウトやダイアラ、未知のプロキシに対応する場合はこれを実装する事になる。golang の
例えば go-uwsgiはこのインタフェースを実装する事で uWSGI インタフェースを実現している。
websocket パッケージはこれを使って http ネゴシエーションを実現している。
実装するのは自由だが、インタフェースが取り決められていないと発散するのでそれを定義しているといった所だろうか。
以上、golang の標準パッケージに含まれる代表的なインタフェースを紹介してみた。
golang のコードがエレガントになるかどうかの半分は、このインタフェースにかかっている。ぜひ極めましょう。
io.Reader
type Reader interface {ご存じ
Read(p []byte) (n int, err error)
}
io.Reader
。このシグネチャの Read を実装しておけば golang のありとあらゆる入力機能に対して自分のコードを提供する事が出来る。
例えば永遠に「おっぱい」と言い続ける Reader だと以下の様な実装になる。
package mainただしこういうことをする場合は、RuneReader を使った方が綺麗かもしれない。
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{})
}
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
for n, _ := range p {
switch w.last {
case '', '\t', '\r', '\n', 0:
if 'a' <= p[n] && p[n] <= 'z' {
p[n] -= 32
}
}
nw, err := w.w.Write(p[n : n+1])
if err != nil {
return r + nw, err
}
w.last = p[n]
}
return r, nil
}
func NewPascalCaseWriter(w io.Writer) *pascalCaseWriter {
return &pascalCaseWriter{w, 0}
}
func main() {
w := NewPascalCaseWriter(os.Stdout)
fmt.Fprintln(w, "hello world")
}
io.Closer
type Closer interface {
Close() error
}
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 {ある入力から1バイトずつ読み込む事が出来るインタフェース。tail 的な処理を書きたい場合に用いる。
ReadByte() (c byte, err error)
}
その他、組み合わせで用いる事が出来る
io.ReadWriteCloser
io.ReadSeeker
io.WriteSeeker
io.ReadWriteSeeker
io.ByteScanner
io.ByteWriter
io.RuneReader
io.RuneScanner
がある。compress/flate/inflate.Reader
type Reader interface {compress 圧縮の Reader を提供する。圧縮されたファイル等をこれを用いて Read すると等価的に内容が読めるという物。
io.Reader
io.ByteReader
}
実際には
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 {Execer と同様に、結果を得るクエリの場合
Query(query string, args []Value) (Rows, error)
}
Prepare
して Query
する必要があるが、データベース接続から直接 Query 出来るドライバである事を明示する事が出来る。ドライバの実装は必須ではない。
database/sql.ColumnConverter
type ColumnConverter interface {golang の
ColumnConverter(idx int) ValueConverter
}
database/sql
は自動で型変換を行ってはくれるが、ある特殊な型に対して変換を行いたい場合にはこのインタフェースを実装しする。このインタフェースを実装する場合、ValueConverter も実装する必要がある。database/sql.ValueConverter
type ValueConverter interface {各型に対する変換処理を提供するインタフェース。例えば bool 型から各型への変換を提供する boolType では以下の実装になっている。
ConvertValue(v interface{}) (Value, error)
}
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 {golang のデータベースドライバを実装するパッケージに対して golang 側から値を要求する際に用いられるインタフェース。
Value() (Value, error)
}
ドライバの実装は必須ではない。
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 {golang には標準で Gob というシリアライザブルフォーマットに対するインタフェースが提供されているが、インスタンスに対して Gob フォーマットへの交換が可能かどうかを明示出来る。
GobEncode() ([]byte, error)
}
encoding/gob.GobDecoder
type GobDecoder interface {同様に Gob からある型への交換が可能かどうかを明示出来る。
GobDecode([]byte) error
}
encoding/json.Unmarshaler
type Unmarshaler interface {JSON を示すバイト列からある型への交換が可能である事を示す。
UnmarshalJSON([]byte) error
}
encoding/json.Marshaler
type Marshaler interface {Unmarshaler とは逆にバイト列からある型への交換が可能である事を示す。
MarshalJSON() ([]byte, error)
}
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)
}
// {"Name": "ウルトラマン", "Password": "aGVuc2hpbg=="}
fmt.Println(string(b))
err = p.UnmarshalJSON([]byte(`
{
"Name": "仮面ライダー",
"Password": "aGVuc2hpbg=="
}
`))
if err != nil {
log.Fatal(err)
}
// 仮面ライダー henshin
fmt.Printf("%s : %s\n", p.Name, p.Password)
}
encoding.xml.Unmarshaler
encoding.xml.Marshaler
も json と同様。flag.Getter
type Getter interface {golang の flag パッケージを用いて、プログラム引数から独自の型へ変換を行いたい場合に使う。
Value
Get() interface{}
}
fmt.Formatter
type State interface {
// Write is the function to call to emit formatted output to be printed.
Write(b []byte) (ret int, err error)
// Width returns the value of the width option and whether it has been set.
Width() (wid int, ok bool)
// Precision returns the value of the precision option and whether it has been set.
Precision() (prec int, ok bool)
// Flag reports whether the flag c, a character, has been set.
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 {独自のカラーパレットを実装したい場合に使用する。gif は 256 色しか出力出来ないのでこれを使って減色処理を行っている。
Quantize(p color.Palette, m image.Image) color.Palette
}
Drawer
はこのパレットを用いて描画を行う際に実装する。image/jpeg.Reader
type Reader interface {jpeg を自前でデコードしたい場合に実装する。
io.Reader
ReadByte() (c byte, err error)
}
net/http.RoundTripper
type RoundTripper interface {http のトランスポート層を抽象化する為に用いられるインタフェース。
RoundTrip(*Request) (*Response, error)
}
デフォルトのタイムアウトやダイアラ、未知のプロキシに対応する場合はこれを実装する事になる。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 {golang 標準の Web サーバ機能を使う場合にレスポンスを書き込むインタフェース。実際にはこれをユーザ側が実装する事はまずない。主にテスト結果を得る為に用いられる。
Header() Header
Write([]byte) (int, error)
WriteHeader(int)
}
net/http.Flusher
内部処理やテストで用いられる。net/http.Hijacker
type Hijacker interface {golang の http 通信は大部分が隠ぺいされている。しかし自前で接続を切ってしまったり、https の connect メソッドを実現する場合には都合が悪いインタフェースだ。そこで
Hijack() (net.Conn, *bufio.ReadWriter, error)
}
req.Hijack()
を呼び出し、http 以外の通信を行う事が出来る。websocket パッケージはこれを使って http ネゴシエーションを実現している。
net/http.CloseNotifier
type CloseNotifier interface {
CloseNotify() <-chan bool
}
ResponseWriter
を自前実装した場合、クライアントから接続を切られた事を検知する為に実装するインタフェース。net.Listener
type Listener interface {自前実装をソケットとして扱わせる為に実装するインタフェース。上記で紹介した go-uwsgi でも自前で Accept を実装する事で http サーバとの間の通信層で uWSGI を喋っている。
Accept() (c Conn, err error)
Close() error
Addr() Addr
}
sync.Locker
type Locker interface {
Lock()
Unlock()
}
sync.Mutex
が実装しているが、自前でもロックインタフェースを実装したい場合にインプリメントする。実装するのは自由だが、インタフェースが取り決められていないと発散するのでそれを定義しているといった所だろうか。
以上、golang の標準パッケージに含まれる代表的なインタフェースを紹介してみた。
golang のコードがエレガントになるかどうかの半分は、このインタフェースにかかっている。ぜひ極めましょう。