Edited at

インターフェースの基礎

More than 5 years have passed since last update.

インターフェースとは、メソッド群を定義する型のことで、

type myInterface interface {

Foo() int
Bar() string
}

のように定義します。type キーワードを使って名前付けを行わなくとも、

func Func(v interface {

Method()
}) {
// ...
}

のように使用することができます。

インターフェース型は他の型とは違い、無名有名に関わらず、代入元が代入先で定義されているメソッド群を持っていれば、そのまま代入することができます。

type typeA interface {

Dummy()
}
type typeB interface {
Dummy()
}
var a typeA
var b typeB
a = b
_ = a // not used エラーの回避

※Go言語の型宣言はトップレベルだけでなく関数内でも可能です。

上のコードから、型名の異なる ab が代入可能であることが確認できます。

type による名前付けは事実上、単にインターフェースへのショートカットを作成しているだけなのです。


インターフェース変数に代入できるのは、もちろんインターフェース型の値だけではありません。

例えば構造体がインターフェースで定義されたメソッド群を持っていれば、その構造体はインターフェース変数に代入できます。これを「(構造体は)インターフェースを実装している」と言います。

実装には同じメソッド群を「持っている」ことが条件であって、「メソッド数も含め完全一致している」必要はありません。

ちなみに、Go言語では「インターフェース○○を実装している」と明示することはありません。

インターフェースを実装しているかどうかは、持っているメソッド群から暗黙的に判断されます。

package main

import "fmt"

type NameGetter interface {
Name() string
}

type Foo struct{}

// この宣言の形式により、このName関数はFoo型が持つメソッドとなる
func (f Foo) Name() string {
return "foo"
}

type Bar struct{}

func (b Bar) Name() string {
return "bar"
}

func main() {
var ng NameGetter
ng = Foo{} // インターフェース型への変換は暗黙的に行われる
fmt.Println(ng.Name())
ng = Bar{}
fmt.Println(ng.Name())
}

出力:


foo

bar


この出力から解るように、ng.Name() によって型 FooBarName() string メソッドがそれぞれ呼び出されています。

インターフェース変数にある値を代入すると、その値の型が持っている、インターフェースで定義されたメソッドを呼び出すことができるようになるのです。


要するに、インターフェースとは扱う要素を「メソッド(群)」に絞り他の要素を省くことで、「特定の型でなくてはならない」という制限を解消する仕組みなのです。

例えば、標準パッケージ io で定義されているインターフェース WriterReader を実装することで、型を問わず対応する機能が標準パッケージには多く存在しています。

色々いじってみましょう。

気が向いたら次回は、インターフェースの別の使い方や、インターフェースを実装する際の手段についての記事を書こうかな、と思っています。

自分が書いた記事を読み返してみると毎度クドいような気がするので、簡潔に書けるようになりたいですが……見込みなし。