気まぐれ更新

Goのinterfaceについて簡単にまとめてみた

goを使い始めて1ヶ月ほど経ち、interfaceについて自分の理解が曖昧だったところがあったため 簡単に調べてまとめてみました。


goにおける型

まずgoにおける型は大きく分けて以下の2つに大別されます。 - 具象型: int, string, array, funcなどの値の表現や操作する機構が性格に定まっているもの - 抽象型: 今回紹介するインターフェース。具象型と対象的に内部構造や操作機構の中身を公開していないもの

interfaceでできること

  • なんでも入れられる型として変数や引数を定義できる
  • 関数の集合体として定義し、ポリモーフィズムを実現できる

ゆるい型としてのinterface

  • 空のインターフェースであるinterface{}を使用することで、任意の型を扱える変数を作ることができる
  • C++でいうところのtemplate関数のようなものを実現できる
package main

import "fmt"

func print(i interface{}) {
    fmt.Printf("print: %v\n", i)
}

func main() {
    var i interface{}
    i = 1
    print(i)
    i = "hello world"
    print(i)
    i = true
    print(i)

}

interfaceにおける型変換

interface{} 型で宣言した変数について、任意の型変換を行う場合以下のような形で記述します。 - (返り値), (変換結果[真偽値]) := (interfaceの変数).(型名)

package main

import "fmt"

func print(s string) {
    fmt.Printf("print: %v\n", s)
}

func main() {
    var i interface{} = "world"
    var s string = "hello"
    is, ok := i.(string)
    print(is)
    print(s)
    print(i) // interface{} 型は暗黙で変換されないためエラー
}

interface{} の型判定

interface{} 型の変数に実際にどのような方が入っているかは型変換と同様の記法で.(type)と書くことで型情報を得ることができます。 - (interface{}型).(type)

package main

import "fmt"

func main() {
    var i interface{}
    i = "hello world"

    switch i.(type) {
    case int:
        fmt.Println("this interface{} is int.")
    case string:
        fmt.Println("this interface{} is string.")
    default:
        fmt.Println("this interface{} is unknown type.")
    }
}

slice, mapに適用

sliceやmapにinterface{} で定義することにより、いくつかの動的型付け言語のように一つの変数に複数の型の情報を格納することができます。 - key, valueともに任意の型をセットすることが可能になる(map) - 任意の型の値を格納することが可能になる(slice)

package main

import "fmt"

func main() {
    // interface{} map
    i := map[interface{}]interface{}{}

    i["hoge"] = 123
    i["huga"] = true
    i[true] = "piyo"
    i[456] = false
    fmt.Println(i)

    // interface{} slice
    arr := []interface{}{}
    arr = append(arr, 123)
    arr = append(arr, "hoge")
    arr = append(arr, true)
    arr = append(arr, func(i int) string { return fmt.Sprintf("%d", i) })

    fmt.Println(arr)
}

関数の集合体としてのinterface

一般的に認知されているインターフェースとしての使い方になります。 - 複数の関数を束ねる一つのものとして定義することが可能 - Javaのインタフェースに似たようなもの - Javaのようにimplements宣言を行う必要はなく、構造体をレシーバーに取るようにメソッドを実装してあげればOKです - インタフェース内で定義されているメソッドが全て実装されている場合、実装主である構造体もしくは関数をそのインタフェースの型として代入可能

package main

import (
    "fmt"
)

type audio interface {
    play()
    stop()
}

type walkman struct{}

type ipod struct{}

func (i *ipod) play() {
    fmt.Println("ipod: play music!!")
}

func (w *walkman) play() {
    fmt.Println("walkman: play music!!")
}

func (i *ipod) stop() {
    fmt.Println("ipod: stop music")
}

func playAndStop(a audio) {
    a.play()
    a.stop()
}

func main() {
    var a audio
    a = &ipod{}
    a = &walkman{} // *walkman does not implement audio (missing stop method)
    playAndStop(a)
}

interfaceの使い方や特徴を頭に入れておくと、go標準のライブラリやOSSパッケージの実装を追いやすくなり 実装にも幅が出てくるのかな、と思います。 自分はまだまだですが。。