インタフェース(interface)
インタフェース(interface)は、メソッドの型宣言の集合であり、どのようなメソッドを実装すべきかを示す仕様です。 この仕様を満たす任意の値を保持できるので、多様な型を扱うことができます。
インタフェースの書き方
インタフェースの型は次の形式で宣言します。
interface {
メソッド名(引数宣言) 戻り値宣言
メソッド名(引数宣言) 戻り値宣言
・・・
}
サンプルコードは次の通りです。
インターフェースPrinter
をReactangle
とPoint
が実装しています。
他のプログラミング言語で見られる、インターフェース実装の宣言は必要ありません。
type Printer interface {
Print()
}
type Rectangle struct {
Width int
Height int
}
func (r Rectangle) Print() {
fmt.Println("Width:", r.Width, "Height:", r.Height)
}
type Point struct {
X int
Y int
}
func (p Point) Print() {
fmt.Println("X:", p.X, "Y:", p.Y)
}
func main() {
var p Printer
p = Rectangle{Width: 2, Height: 3}
p.Print()
p = Point{X: 3, Y: 2}
p.Print()
}
インターフェースの埋め込み
インターフェースの宣言に、別のインターフェースの仕様を埋め込むことができます。 他のインターフェースと共通する振る舞いがある際に活用できます。
type Formatter interface {
Format() string
}
type Printer interface {
Formatter // インターフェースFormatterを埋め込む
Print()
}
type Rectangle struct {
Width int
Height int
}
func (r Rectangle) Format() string {
return fmt.Sprintf("Width: %d, Height: %d", r.Width, r.Height)
}
func (r Rectangle) Print() {
fmt.Println(r.Format())
}
func main() {
var p Printer
p = Rectangle{Width: 2, Height: 3}
fmt.Println(p.Format()) // Width: 2, Height: 3
p.Print() // Width: 2, Height: 3
}
空のインターフェース(interface{})
空のインターフェースinterface{}
は、任意の値を保持できます。
実装すべきメソッドがないためです。
// 空のインターフェースは任意の値を代入できる
i := interface{}("a") // stringを代入できる
i = 1 // intを代入できる
i = true // boolを代入できる
型アサーション
インターフェース型の変数から本来の型を取り出すには、型アサーションを使います。
値は変数.(型)
の形式で取り出します。(例:v, ok := i.(int)
)
1つ目の戻り値は取り出した値、2つ目の戻り値は型アサーションの成否です。
type Printer interface {
Print()
}
type Rectangle struct {
Width int
Height int
}
func (r Rectangle) Print() {
fmt.Println("Width:", r.Width, "Height:", r.Height)
}
type Point struct {
X int
Y int
}
func (p Point) Print() {
fmt.Println("X:", p.X, "Y:", p.Y)
}
func main() {
var p Printer
p = Rectangle{Width: 2, Height: 3}
r, ok := p.(Rectangle) // Rectangle型を取得
fmt.Println(r, ok) // {2 3} true
p = Point{X: 3, Y: 2}
_, ok = p.(Rectangle) // 型アサーションを失敗させる
fmt.Println(ok) // false
}
型switch
switch文に変数.(type)
を記述することで、型の種類による分岐処理を表現できます。
i := interface{}("a")
switch v := i.(type) {
case int:
fmt.Println("int:", v)
case float64:
fmt.Println("float64:", v)
case string:
fmt.Println("string:", v)
default:
fmt.Println("other:", v)
}
// 実行結果:
// string: a
インターフェースの実装を保証する方法
ある型が特定のインターフェースを実装しているかを確認するには、変数にゼロ値を代入します。 変数の型は確認したいインターフェースの型にします。
type Formatter interface {
Format() string
}
type Rectangle struct {
Width int
Height int
}
func (r Rectangle) Format() string {
return fmt.Sprintf("Width: %d, Height: %d", r.Width, r.Height)
}
// RectangleがFormatterを実装していることを確認
var _ Formatter = Rectangle{}
よく使われるインターフェース
Goでよく使われるインターフェースを紹介します。
error
error
はGoでプログラムを書くなら、ほぼ必ずと言って良いほど使います。
これも実はインターフェースです。error
はメソッドError() string
を実装する必要があります。
これはオリジナルのerror
を実装したものです。
fmt.Println()
は、渡した値がerror
の場合にError
メソッドの結果を表示します。
type FileNotFoundError struct {
Path string
}
func (e *FileNotFoundError) Error() string {
return fmt.Sprintf("file not found: %s", e.Path)
}
func main() {
var err error
err = &FileNotFoundError{Path: "/path/dummy"}
fmt.Println(err) // file not found: /path/dummy
}
Stringer
Stringer
は値を文字列で表現するインターフェースです。
メソッドString() string
を実装する必要があります。
これを実装していると、fmt.Println
関数でString
メソッドの結果が表示されるようになります。
type Rectangle struct {
Width int
Height int
}
func (r *Rectangle) String() string {
return fmt.Sprintf("Width: %d, Height: %d", r.Width, r.Height)
}
func main() {
r := &Rectangle{Width: 2, Height: 3}
// Stringメソッドの結果が出力される
fmt.Println(r) // Width: 2, Height: 3
}