はじめに
今回は最近実務でも使い出し、学習をしているGo言語のInterfaceについて学習したことをまとめていきたいと思います。
interfaceとは
Interfaceは、JavaやC#などの静的型付け言語に登場する概念で、Go言語にも存在します。
これにより実装とInterfaceを分離することができます。
Interfaceは、メソッドの塊です。
Interfaceを定義するには、interfaceキーワードを使用します。
1 2 3 | type 名前 interface { メソッド メソッドでreturnされる型 } |
Interfaceを定義する際にメソッドは複数定義できます。
interfaceを実装するには、他の言語では明示的に指定する必要がありますが、Go言語では型にメソッドとして全て定義してしまえば、自動的にinterfaceを実装した(要件を満たした)と見なされます。
1 2 3 4 | type Test interface { Hoge() Foo() } |
例えば上記のようなinterfaceを定義した場合、以下のように、TestStructにinterfaceを実装しています。
1 2 3 4 5 6 7 8 | type TestStruct struct {} func (t TestStruct) Hoge() { fmt.Println("hoge") } func (t TestStruct) Foo() { fmt.Println("foo") } |
まとめると以下のようになります。
- インターフェースには実装の詳細を含まない
- 実装と使用を切り離して考えられる
- インターフェースを実装する型にはインターフェースの全てのメソッドが必要となる
- Goでは型にインターフェイスで必要なメソッドがすべて含まれている場合、暗黙的にインターフェースが実装されていると判断される
interfaceのメリット
- インターフェースを使うとコードの共通化ができる
- インターフェースを使うと実装を隠蔽することができる
- インターフェースを使うと単体テストでモックを作れる
インターフェースを使うとコードの共通化ができる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | type Profile interface { GetName() string SetName(name string) } type dog struct { Name string } func NewDog() Profile { return &dog{} } func (t *dog) GetName() string { return t.Name } func (t *dog) SetName(name string) { t.Name = name } type Cat struct { Name string } func NewCat() Profile { return &Cat{} } func (t *Cat) GetName() string { return t.Name } func (t *Cat) SetName(name string) { t.Name = name } func main() { t1 := NewDog() t2 := NewCat() makePet(t1, "ポチ") makePet(t2, "ミケ") fmt.Println(t1.GetName()) fmt.Println(t2.GetName()) } func makePet(t Profile, name string) Profile { t.SetName(name) return t } |
上記で重要な所は、Dog型であろう、Cat型であろうとと同じく名前をセットできており、同じコードで2つの実体を扱うことができている点です。もしこれがインターフェースではなく具体的な構造体を受け取っていると、それぞれの型で2つの makePet() の定義が必要になってしまいます。
インターフェースを使うと実装を隠蔽することができる
Golangではパッケージを境界として関数や構造体の公開・非公開を制御することができます。これを利用しインターフェースの実装部分のみを外部公開することで、中身の構造体や内部処理に使う関数を隠蔽し特定の型・パッケージに依存したコードを書けなくすることができます。
インターフェースを使うと単体テストでモックを作れる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | func makePet(t Profile, name string) Profile { t.SetName(name) return t } type testPet struct { Name string } func (t *testPet) GetName() string { return t.Name } func (t *testPet) SetName(name string) { t.Name = name } func TestMain(t *testing.T) { patterns := []struct { name string }{ {name: "ムギ"}, {name: "ココ"}, {name: "モカ"}, {name: "ソラ"}, } for _, pattern := range patterns { pet := &testPet{} makePet(pet, pattern.name) if pet.GetName() != pattern.name { // エラー処理 } } } |
テストファイル内で新たにtestPet型を作成し、Profileインターフェースのメソッドを実装してます。
こういう形を取ることによりテスト時だけ挙動が異なる実装を用意することができるため単体テストを非常にやりやすくすることができます。
空のinterface
上記はGoのinterfaceの使い方の一例ですが、
他にも、なにもメソッドを要求しない空のinterface型などもあります。
空のinterfaceは何も要求しないので、すべての変数がこのinterfaceを満たしているとも言えます。
そのため、どの型の変数でも代入することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | func main() { var i interface{} i := 123 describe(i) i := "hoge" describe(i) } func describe(i interface{}) { fmt.Println(i) // 出力:123 hoge } |
さいごに
Goで大きなアプリケーションを作成するための機能としてinterfaceを簡単に学習しました。柔軟性の高いコードを書けるよう、深く勉強していきたい、日々精進していきたいと思います。