こんにちは。KOUKIです。
とある企業でWeb系のエンジニアをしています。開発言語はGo言語です。
先日から「[golang]Interfaceのオススメの使い方」について紹介してきました。
今回は、その第三弾で、Interfaceを使って既存のメソッドを暗黙的に実装しカスタマイズする方法を紹介します。
Interfaceの基礎については、以下の記事を参考にしてください!
<目次>
Interfaceの暗黙的な実装
復習ですが、Go言語では
にて、Interfaceを宣言できます。type XXXX interface
そして、Interface内に宣言した関数と同じシグネチャ(名前、パラメータ、戻り値)と同じシグネチャを持つメソッドを定義するとそのInterfaceを暗黙的に実装します。
この機能を利用して、既存の処理をカスタマイズできます。
具体例をあげましょう。
io.Writerの実装
golangのioパッケージには、Writer interfaceが存在します。
1 2 3 |
type Writer interface { Write(p []byte) (n int, err error) } |
このWriter interfaceは、fmtパッケージなどの出力系の関数のパラメータとしてよく実装されています。
例えば、Fprintf関数の第一パラメータはio.Writerであり、Writer interfaceを満たすインスタンスを渡す事ができます。
1 2 3 4 |
// 第一パラメータにWriter Interfaceが定義されている func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { ... } |
サンプルコードを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package main import "fmt" // 対象のインスタンス type ByteCounter int // Writerインターフェースを満たすメソッドを定義 func (bc *ByteCounter) Write(p []byte) (n int, err error) { *bc += ByteCounter(len(p)) return len(p), nil } func main() { var b ByteCounter fmt.Fprintf(&b, "hello world") fmt.Println(b) } |
ByteCounterには、Writeメソッドを定義しました。これにより、Writer interfaceを暗黙的に実装します。
そのため、Fprintf関数の第一パラメータに引数として渡す事が可能になります。
1 2 |
$ go run main.go 11 |
fmt.Stringer
fmtパッケージのStringerインターフェースも知っていると便利です。
1 2 3 |
type Stringer interface { String() string } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package main import "fmt" type user struct { name string email string } func (u user) String() string { return fmt.Sprintf("Custome: %s <%s>", u.name, u.email) } func main() { u := user{ name: "Self Note", email: "selfnote@example.com", } fmt.Println(u) } |
Stringerを暗黙的に実装するとカスタマイズした文字列を出力する事ができます。
1 2 |
$ go run main.go Self Note <selfnote@example.com> |
json.Marshal/Unmarshal
ネットワーク越しに入ってきたデータをGoのStructの中に入れたり、逆にStructのデータをネットワーク越しに送信したりするときに便利なのが、jsonパッケージのMarshalとUnmarshalです。
具体的な使い方は、この記事を参照してください。
Marshalには、Marshaler Interfaceが存在します。
1 2 3 |
type Marshaler interface { MarshalJSON() ([]byte, error) } |
そして、Unmarshalには、Unmarshaler Interfaceが存在します。
1 2 3 |
type Unmarshaler interface { UnmarshalJSON([]byte) error } |
MarshalJSONとUnmarshalJSONを実装したメソッドを定義すれば、既存の処理をカスタマイズできるはずです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
package main import ( "encoding/json" "fmt" ) type Person struct { Name string `json:"name"` Age int `json:"age"` Nicknames []string `json:"nicknames"` } // Marshalのカスタマイズ func (p Person) MarshalJSON() ([]byte, error) { v, err := json.Marshal(&struct { Name string }{ Name: "Mr." + p.Name, }) return v, err } // Unmarshalのカスタマイズ func (p *Person) UnmarshalJSON(b []byte) error { type Person2 struct { Name string `json:"name"` } var p2 Person2 err := json.Unmarshal(b, &p2) if err != nil { fmt.Println(err) } p.Name = p2.Name + "!" return err } func main() { // ネットワーク越しのデータ b := []byte(`{"name": "selfnote", "age": 32, "nicknames":["a","b","c"]}`) var p Person // byte -> struct if err := json.Unmarshal(b, &p); err != nil { fmt.Println(err) } fmt.Println(p.Name, p.Age, p.Nicknames) // struct -> byte v, _ := json.Marshal(p) fmt.Println(string(v)) } |
1 2 3 4 5 6 7 8 9 |
// UnmarshalJSON / MarshalJSONを実装した状態の出力結果 $ go run main.go selfnote! 0 [] {"Name":"Mr.selfnote!"} // UnmarshalJSON / MarshalJSONをコメントアウトした場合の出力結果 $ go run main.go selfnote 32 [a b c] {"name":"selfnote","age":32,"nicknames":["a","b","c"]} |
注意する点としては、UnmarshalJSONの場合はポインタを渡し、MarshalJSONの場合はポインタを渡さないことです。
1 2 3 4 |
// ポインタあり (p *Person) UnmarshalJSON // ポインタなし (p Person) MarshalJSON |
データに不整合が生じるので注意してください。
おわりに
Interfaceを見つけたら目を光らせて、「このインターフェースの便利な使い方は何だろう?」と自問自答すると意外な使い方を発見する事ができるかもしれません。
宝探しに似ていますね。とはいえ、自分もまだ完全に使いこなせてません^^;
プログラミングは、奥が深いですよね!これからもInterfaceの便利な使い方がわかったら記事にしたいと思います。
それでは、また!
最近のコメント