もっとも単純な形では、インターフェース型はメソッドのシグネチャの集まりです。
次は、AddとValueという2つのメソッドを含むインターフェース定義の例です。
type Counter interface {
Add(increment int)
Value() int
}
incrementのような仮引数の名前はインターフェース定義から省略できますが、書いておくと読みやすくなることがよくあります。
Goのインターフェース名には、InterfaceやIという語は含めません。
その代わり、ReaderやStringerのように、erで終わることがよくあります。
インターフェースのメソッドを定義している型は、自動的に、暗黙的にそのインターフェースを「実装」します。
Goにはimplementsというキーワードはありません。
次の型は、上で見たCounterインターフェースを実装しています。
type Stats struct {
value int
// ...
}
func (s Stats) Add(v int) {
s.value += v
}
func (s Stats) Value() int {
return s.value
}
func (s Stats) SomeOtherMethod() {
// The type can have additional methods not mentioned in the interface.
}
インターフェースを実装するうえでは、メソッドが値レシーバーとポインターレシーバーのどちらを持つかは関係ありません。 (これらについて自信がなければ、メソッドの概念をもう一度見てみましょう。)
インターフェース型の値は、それらのメソッドを実装する任意の値を保持できます。 1
つまりStatsは、Counterインターフェースを期待するあらゆる場所で使えるようになりました。
func SetUpAnalytics(counter Counter) {
// ...
}
stats := Stats{}
SetUpAnalytics(stats)
// works because Stats implements Counter
もし関数のすべての仮引数と戻り値が具象型(つまり、インターフェースではない型)を使う必要があるとしたら、関数の再利用性は大きく制限されてしまいます。 仮引数にインターフェース型を使うと、その関数が役割を果たすために必要な振る舞いだけが定義されます。 これで、具象型がそのインターフェースを実装していれば、関数はさまざまな具象型の仮引数とともに使えます。 インターフェース型を戻り値として使う場合も、同じことが言えます。
インターフェースは暗黙的に実装されるので、型は複数のインターフェースを簡単に実装できます。 必要なメソッドがすべて定義されていればよいのです。
インターフェース定義には、他のインターフェースを含めることができます。 次は標準ライブラリからの例です。
type ReadCloser interface {
Reader
Closer
}
// Based on these interfaces:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
ReadCloserを定義するためにReadとCloseのメソッドシグネチャを繰り返すのではなく、このインターフェース定義ではReaderとCloserの既存の定義を利用しています。
Goにはとても特別なインターフェース型が1つあります。メソッドを1つも含まない空のインターフェース型です。
空のインターフェースはinterface{}のように書きます。
Go 1.18以降では、anyも使えます。
anyはエイリアスとして定義されました。
空のインターフェースにはメソッドがないので、すべての型が暗黙的にこれを実装します。 これは、どんな値でも汎用的に受け取れる関数を定義するのに役立ちます。 その場合、関数の仮引数には空のインターフェース型を使います。
例えば、次のとおりです。
func AnythingGoes(i interface{}) string {
if i == 0 {
return "zero"
}
if i == "" {
return "empty string"
}
return "something else"
}