Golangはインターフェイスをサポートしており、暗黙のうちに実装されています。
また、インターフェイスを使用することでGolnagにおけるポリモーフィズムを可能にします。
この記事では、インターフェイスについて、インターフェイスとは何か、どのように使用できるかを説明します。
インターフェース(interface)とは?
インターフェイスとは、Goのポリモーフィズムを実現する抽象的な概念であり、インターフェースの変数は、その型を実装した値を保持することができます。
型アサーションは基礎となる具体的な値を取得するために使用されます。
Golangでインターフェイスを定義・宣言する方法
インターフェースは型として宣言されます。
以下は、インターフェイスを宣言する時の基本的な書き方です。
type インターフェース名 interface {}
インターフェース(interface)のゼロ値
インターフェースのゼロ値はnilです。つまり、何の値も型も持たないということです。
以下がインターフェースのゼロ値を出力しているコードになります。
package main
import (
"fmt"
)
func main() {
var testInterface interface{}
fmt.Println(testInterface) // <nil>
}
Golangの空のインターフェイス
関数を全く持たないインターフェースは空でであり、空のインターフェースはどんな型でも保持します。
そのため、からのインターフェースは汎用的で非常に便利です。
以下が、空のインターフェースを定義する書き方です。
var testInterface interface {}
Golangでインターフェイスを実装する
インターフェイスは、そのインターフェイスの関数を型が実装している場合に実装されます。
以下では,インターフェイスを実装する方法を示す例を示します.
package main
import (
"fmt"
)
type Person interface {
greet() string
}
type Human struct {
Name string
}
func (human *Human) greet() string {
return "Hi, I am " + human.Name
}
func isHuman(human Person) {
fmt.Println(human.greet())
}
func main() {
var tanaka = Human{"Tanaka"}
fmt.Println(tanaka.greet()) // Hi, I am Tanaka
isHuman(&tanaka) // Hi, I am Tanaka
}
Goで複数のインターフェイスを実装する
複数のインターフェースを同時に適応することもできます。
もしすべての関数が実装されていれば、その型はすべてのインターフェースを実装していることになります。
以下のBird型(構造体)は,関数をfly関数とwalk関数を実装することで両方のインタフェース(Flyer型・Walker型)を実装しています。
package main
import (
"fmt"
)
type Flyer interface {
fly() string
}
type Walker interface {
walk() string
}
type Bird struct {
Name string
}
func (b *Bird) fly() string {
return "飛びます"
}
func (b *Bird) walk() string {
return "歩きます"
}
func main() {
var bird = Bird{"piyo"}
fmt.Println(bird.fly()) // 飛びます
fmt.Println(bird.walk()) // 歩きます
}
インターフェース同士を合成する
インターフェースは一緒に構成することができます。この構成は、ソフトウェア開発において最も重要な概念の一つです。
複数のインタフェースが実装されている場合、その型は合成を行ったことになり、ポリモーフィズムが必要な場合に非常に有効です。
インターフェースにおける値
インターフェース値には、具象型と動的型があります。
package main
import (
"fmt"
)
type Flyer interface {
fly() string
}
type Walker interface {
walk() string
}
type Bird struct {
Name string
}
func (bird *Bird) fly() string {
return "飛びます"
}
func (bird *Bird) walk() string {
return "歩きます"
}
func main() {
var bird = Bird{"piyo"}
fmt.Printf("%v: %T", b, b) // {piyo}: main.Bird
}
上記のコードの変数birdはBird型だが、中身の具体的な値は{piyo}です。
インターフェイスを使った型アサーション
型注釈(型アサーション)は、インターフェイスが保持する基本的な値を取得する方法です。
つまり、インターフェース変数に文字列が代入される場合、その変数が保持する基礎的な値は文字列であるということです。
以下は、インターフェイスを使った型アサーションの使い方を示すコード例です
package main
import (
"fmt"
)
type Test struct {
s string
i int
}
func main() {
var i interface{} = Test{"Hello, world", 100}
fmt.Println(i) //{Hello, world 100}
fmt.Println(i.(Test)) //{Hello, world 100}
fmt.Println(i.(Test).s) //Hello, world
fmt.Println(i.(Test).i) //100
}
インターフェイスを使った型switch
型switchは、switch caseと極めて類似した制御構造であるが、唯一の違いは異なる条件を切り替えるためにインターフェース型を使用することです。
package main
import (
"fmt"
)
func checkType(i interface{}) {
switch i.(type) { // switchはインターフェイスの型を使用する
case int:
fmt.Println("Int")
case string:
fmt.Println("String")
default:
fmt.Println("Other")
}
}
func main() {
var i1 interface{} = "文字列です"
var i2 interface{} = 100
var i3 interface{} = true
checkType(i1) // String
checkType(i2) // Int
checkType(i3) //Other
}
インターフェース値の等価性
以下に示す条件のいずれかを満たす場合、インターフェース値は等しくなります。
- 両方ともnilである
- 両者とも同じ基礎となる具象値および同じ動的型を持っている
以下のコードでは、2つのインターフェース型の値を比較して等しいか確かめるisEqual関数を定義してます。
package main
import (
"fmt"
)
func isEqual(i interface{}, j interface{}) {
if i == j {
fmt.Println("等しい")
} else {
fmt.Println("異なる")
}
}
func main() {
var i1 interface{}
var i2 interface{}
isEqual(i1, i2) // 等しい
var i3 interface{} = 100
var i4 interface{} = 100
isEqual(i3, i4) // 等しい
}
関数でインターフェースを使う
インターフェースは、他の型と同じように関数に渡すことができます。インターフェイスを使用する際の大きな利点は、どんなタイプの引数でも使用できることです。
以下は、関数の引数においてインターフェイスの使い方を示すコード例です。
package main
import (
"fmt"
)
func sample(i interface{}) {
fmt.Printf("%T\n", i)
}
func main() {
var a interface{} = "文字列です"
var c int = 100
sample(a) // string
sample(c) // int
}
まとめ
インターフェイスは、Golangにおけるポリモーフィズムを実現することができます。
複数の型を渡すことができる関数では、インターフェイスを使用することができますし、素晴らしい機能なので、上手く使いこなしていきましょう。