インタフェース(interface)

インタフェース(interface)は、メソッドの型宣言の集合であり、どのようなメソッドを実装すべきかを示す仕様です。 この仕様を満たす任意の値を保持できるので、多様な型を扱うことができます。

インタフェースの書き方

インタフェースの型は次の形式で宣言します。

interface {
    メソッド名(引数宣言) 戻り値宣言
    メソッド名(引数宣言) 戻り値宣言
    ・・・
}

サンプルコードは次の通りです。 インターフェースPrinterReactanglePointが実装しています。

他のプログラミング言語で見られる、インターフェース実装の宣言は必要ありません。

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
}