こんにちは。Go入門ブログの第13回です。
本記事では、構造体とならんでGoプログラミングの中心的な機能をもつインターフェース(interface)について解説します。
インターフェースは型の一種であり、任意の型がどのようなメソッドを実装するべきかを規定する枠組みです。
Goにおいて型の柔軟性を担保するための枠組みでもあり、非常に重要な要素ですのでしっかり抑えておきましょう。
interface{}型変数とnil
まず、intercace{}型について解説していきましょう。
intercace{}は、{}まで含めて型の名称です。
intercace{}型は、int型やstring型といった、Goにおけるあらゆる型と互換性があります。
JavaやC#といったオブジェクト指向言語におけるObject型のようなものだと考えてよいでしょう。
interface型の変数は、基本型の変数と同様にvarステートメントで定義できます。
また、intercace{}型の初期値はnilです。
nilはGoにおいて、値を持っていない状態を表す特殊な値です。
Java等におけるnullに相当するものと考えて相違ありません。
interface{}型の互換性
先に述べたとおり、intercace{}型はすべての型と互換性があり、あらゆる値を汎用的に表すことができます。
ただし、intercace{}型の変数に格納された値は、演算を行うことができないので注意が必要です。
var x, y interface{} = 1, 2 z := x + y // 演算できないためコンパイルエラーとなる
インターフェース型の定義
先の例ではinterface{}型変数の扱いをみましたが、次は型の定義方法をみてみましょう。
型定義の宣言は他の型と同様に、typeステートメントを使用します。
シグニチャ1
シグニチャ2
シグニチャ3
. . . .
}
インターフェース型は上記の様に、メソッドのシグニチャ(メソッド名、引数、戻り値の型)の列挙で定義します。
インターフェース型の実体は、メソッドのシグニチャの集合です。
インターフェースの活用
インターフェースの最も汎用的な活用方法は、2つ以上の異なる型に共通のメソッドを付与することです。
実例を見てみましょう。
package main import ( "fmt" ) // Stringfyインターフェース type Stringfy interface { ToString() string } // Person構造体 type Person struct { Name string Age int Gender string } // Person構造体のメソッドToString func (person *Person) ToString() string { return fmt.Sprintf("%s(%d歳:%s)", person.Name, person.Age, person.Gender) } // Animal構造体 type Animal struct { Name string Kind string } // Animal構造体のメソッドToString func (animal *Animal) ToString() string { return fmt.Sprintf("%s[%s]", animal.Name, animal.Kind) } func main() { vs := []Stringfy{ &Person{Name: "山田太郎", Age: 20, Gender: "男"}, &Animal{Name: "牛", Kind: "哺乳類"}, } for _, v := range vs { fmt.Println(v.ToString()) } }
このサンプルの実行結果は以下のようになります。
山田太郎(20歳:男)
牛[哺乳類]
この例では、定義のまったく異なる2つの構造体PersonとAnimalを定義しています。
また、Stringfyインターフェースの中でToStringメソッドを定義し、このメソッドを2つの構造体にそれぞれ実装しています。
これにより、Person型とAnimal型はToStringメソッドを実装しているという共通点を持ち、結果としてStringfy型のデータとしてまとめて処理できようになっています。
このように、型の性質を抽出したインターフェースを定義することで、Goの厳密な型システムに柔軟性を持たせることができます。
Goの代表的なインターフェース
インターフェースの用法として、実例をみるとより理解が深まります。
Goで特に使用頻度の高い、代表的な組み込み型インターフェースを見ていきましょう。
errorインターフェース
Goの最も代表的な組み込みインターフェースの一つがerrorです。
Goのプログラムは、エラーの状態をerror値で表現します。
Goの組み込み型であるerrorは、インターフェースとして以下のように定義されています。
Error() string
}
Goでは、関数やメソッドの戻り値として、このerror型が頻繁に使用されます。
実体としては、パッケージや方に応じて独自のエラー型が定義されていますが、それらの独自型はerrorインターフェースによって隠蔽されています。
この仕組みにより、Goのエラー処理はerror型をどのように取り扱うかという点に共通化することができているのです。
それでは、errorインターフェースを利用し、独自のエラー型を定義するサンプルを見てみましょう。
package main import ( "fmt" "time" ) // 独自定義のエラー構造体 type MyError struct { Code int Message string When time.Time } // errorインターフェースのメソッドを定義 func (e *MyError) Error() string { return fmt.Sprintf("%v [エラーコード: %d] %s", e.When, e.Code, e.Message) } // エラーを発生させる関数 func run() error { return &MyError{ Code: 8888, Message: "エラーが発生しました", When: time.Now(), } } func main() { // エラーを発生させる if err := run(); err != nil { fmt.Println(err) } }
このサンプルの実行結果は以下のようになります。
2019-07-26 07:14:36.758954 +0900 JST m=+0.003000401 [エラーコード: 8888] エラーが発生しました
このサンプルでは、9行目で構造体MyErrorを定義し、14行目でerrorインターフェースが要求するError()メソッドを定義しています。
これだけで、インターフェースの実装は完了です。
構造体やメソッドの定義において、任意のインターフェースを実装するための特別な宣言などは要求されません。
これの定義により、22行目の関数runが返すerror型の戻り値として、MyError型を使用することが可能になっています。
fmt.Stringerインターフェース
もうひとつ、Goでもっともよく使用されるインターフェースを見てみましょう。
fmtパッケージに定義されているStringer型です。
Stringerは極めてシンプルなインターフェースで、以下のように文字列を返すメソッドStringだけが定義されています。
String() string
}
fmtパッケージに属する関数は、様々なデータ型をinterface{}型として受け取り、それを適切な書式へ変換して出力する機能を有しています。
更に、Stringerインターフェースを活用することで、ユーザ任意の文字列書式に出力内容をカスタマイズすることができます。
それではサンプルを見てみましょう。
package main import "fmt" // Member構造体 type Menber struct { Number int // 会員番号 Name string // 名前 Age int // 年齢 Gender string // 性別 } // StringerインターフェースのStringメソッドを実装し、任意の出力書式を定義 func (m Menber) String() string { return fmt.Sprintf("【会員番号%d】%s (%d歳/%s)", m.Number, m.Name, m.Age, m.Gender) } func main() { member := Menber{ Number: 1234, Name: "山田太郎", Age: 32, Gender: "男性", } fmt.Println(member) }
このサンプルの実行結果は以下のようになります。
【会員番号1234】山田太郎 (32歳/男性)
fmt.Printlnが出力する内容が、構造体Menber型に定義したメソッドStringが返す文字列書式に変化していることが確認できます。
このようにfmtパッケージは、fmt.Stringerインターフェースを実装した型であれば、メソッドStringが返す文字列を出力に使用してくれます。
終わりに
本記事では、Goのインターフェースについて詳細な解説を行いました。
本記事のサンプルコードを見るとおわかり頂けると思いますが、インターフェースは、任意の型に実装すべきメソッドを定義するものである性質上、構造体と密接な関わりを持ちます。
構造体とインターフェースを自在に活用できるようになれば、ほぼGoプログラミングを習得したも同然です。
ぜひしっかりと理解して使いこなしてください。