今回は、Interfaceについて学びましょう。
<目次>
学習記事まとめ
序章

Interfaceに触れる前に、少しお付き合いください。
次のディレクトリとファイルを用意してください。
1 2 |
mkdir gadget touch gadget/tape.go |
tape.goにテーププレイヤーとテープレコーダーのStructとそれぞれのメソッドを実装してみます。
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 |
package gadget import "fmt" type TapePlayer struct { Batteries string } func (t TapePlayer) Play(song string) { fmt.Println("Playing", song) } func (t TapePlayer) Stop() { fmt.Println("Stopped!") } type TapeRecorder struct { Microphones int } // TapePlayerと同じメソッド func (t TapeRecorder) Play(song string) { fmt.Println("Playing", song) } func (t TapeRecorder) Record() { fmt.Println("Recording") } // TapePlayerと同じメソッド func (t TapeRecorder) Stop() { fmt.Println("Stopped!") } |
テーププレイヤーとテープレコードの挙動は、Record関数以外は同じです。
続いて、TapePlayerの挙動を確認するため、main関数を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package main import "github.com/hoge/gadget" func playList(device gadget.TapePlayer, songs []string) { for _, song := range songs { device.Play(song) } device.Stop() } func main() { player := gadget.TapePlayer{} mixtape := []string{"Jessie's Girl", "Whip It", "9to5"} playList(player, mixtape) } |
main関数にてTapePlayerのStructを宣言し、mixtape(音楽リスト)と共にplayList関数へ引数として渡します。
1 2 3 4 5 6 |
$ go run main.go Playing Jessie's Girl Playing Whip It Playing 9to5 Stopped! |
問題なく動きましたね。
今度は、TapeRecorderの挙動を確認してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package main import "github.com/hoge/gadget" func playList(device gadget.TapePlayer, songs []string) { for _, song := range songs { device.Play(song) } device.Stop() } func main() { player := gadget.TapeRecorder{} mixtape := []string{"Jessie's Girl", "Whip It", "9to5"} playList(player, mixtape) } |
1 2 3 4 |
$ go run main.go # command-line-arguments ./main.go:15:10: cannot use player (type gadget.TapeRecorder) as type gadget.TapePlayer in argument to playList |
これはコンパイルエラーになります。playListの第一引数がTapePlayerだからです。
どちらも似たような実装なので、区別なく動いて欲しいです。
Go言語にはこの問題を解決するために、Interfaceが用意されています。
Interfaceとは
Interfaceは、JavaやC#などの静的型付け言語に登場する概念で、Go言語にも存在します。
Interfaceは、特定の処理を持つことを期待されたメソッドの塊です。
Interfaceを定義するには、interfaceキーワードを使用します。
1 2 3 4 5 6 |
type myInterface interface { methodA() methodB(float64) methodC()string methodD(string)string } |
Go言語のインターフェースの実装は、結構簡単です。
サンプルコードを以下に記載します。
1 2 |
mkdir mypkg touch mypkg/myinterface.go |
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 |
package mypkg import "fmt" type MyInterface interface { MethodWithoutParameters() MethodWithParameter(float64) MethodWithReturnValue() string } // Declear a type. We'll make it satisfy myInterface type MyType int func (m MyType) MethodWithoutParameters() { fmt.Println("MethodWithoutParameters called") } func (m MyType) MethodWithParameter(f float64) { fmt.Println("MethodWithParameter called with", f) } func (m MyType) MethodWithReturnValue() string { return "Hi from MethodWithReturnValue" } func (m MyType) MethodNotInInterface() { fmt.Println("MethodNotInINterface called") } |
interfaceを実装するには、他の言語では明示的に指定する必要がありますが、Go言語ではメソッドとして全て定義してしまえば、自動的にinterfaceを実装した(要件を満たした)と見なされます。
上記の例では、MyTypeにinterfaceを実装しています。
呼び出し方も覚えてしまいましょう。main関数を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package main import ( "fmt" "github.com/hoge/mypkg" ) func main() { // Declare a variable using the interface type var value mypkg.MyInterface value = mypkg.MyType(10) value.MethodWithoutParameters() value.MethodWithParameter(10.2) fmt.Println(value.MethodWithReturnValue()) } |
まず、interface typeの変数を宣言し、そこにinterfaceのメソッドを全て実装したMyTypeを代入すればOKです。
プログラムを実行してみましょう。
1 2 3 4 5 |
$ go run main.go MethodWithoutParameters called MethodWithParameter called with 10.2 Hi from MethodWithReturnValue |
Interfaceへ格納できる値
前述のように、interface typeを伴う変数を宣言した場合、この変数には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 |
package main import "fmt" // Type1 type Whistle string // MakeSound 1 func (w Whistle) MakeSound() { fmt.Println("Weeeeeee!!!!") } // Type2 type Horn string // MakeSound2 func (h Horn) MakeSound() { fmt.Println("Yahooooooo!!!") } // Declare Interface type NoiseMaker interface { MakeSound() } func main() { var toy NoiseMaker toy = Whistle("Wow Wow!!!") toy.MakeSound() toy = Horn("Yumm!!!") toy.MakeSound() } |
このサンプルコードには、NosieMaker interfaceのメソッド(MakeSound)を継承した変数「Whistle」および「Horn」を用意しました。
注目して欲しいのが、main関数内の変数の割り当てです。
interface typeと共に宣言したtoy変数にWhistle、Hornの両方の値を格納できています。
これは、どちらもNoiseMaker interfaceのMakeSoundメソッドを継承しているからです。
さらに一歩、理解を深めましょう。
実は、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 |
package main import "fmt" // Type1 type Whistle string // MakeSound 1 func (w Whistle) MakeSound() { fmt.Println("Weeeeeee!!!!") } // Type2 type Horn string // MakeSound2 func (h Horn) MakeSound() { fmt.Println("Yahooooooo!!!") } // Declare Interface type NoiseMaker interface { MakeSound() } func play(n NoiseMaker) { n.MakeSound() } func main() { play(Whistle("Whistle!!!!")) play(Horn("Horn!!!")) } |
この場合もinterfaceの要件を満たせていれば、playの引数には何を渡しても問題ありません。
1 2 3 4 |
$ go run main.go Weeeeeee!!!! Yahooooooo!!! |
一見便利そうですが、注意点もあります。
前述の通り、interface typeの変数からinterfaceに定義したメソッドの呼び出しが可能です。
先ほどのサンプルコードに新しくRobot typeを宣言して、MakeSoundメソッドの他にWalkメソッドも定義してみましょう。
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 |
package main import "fmt" // Type1 type Whistle string // MakeSound 1 func (w Whistle) MakeSound() { fmt.Println("Weeeeeee!!!!") } // Type2 type Horn string // MakeSound2 func (h Horn) MakeSound() { fmt.Println("Yahooooooo!!!") } // Type3 type Robot string func (r Robot) MakeSound() { fmt.Println("Roboooooooo!!!") } func (r Robot) Walk() { fmt.Println("Walking is a lot of fun!!!") } // Declare Interface type NoiseMaker interface { MakeSound() } func play(n NoiseMaker) { n.MakeSound() n.Walk() // new } func main() { play(Robot("I'm a Robot")) } |
Walkメソッドは、新しく作成したRobotにしかありません。
このWalkメソッドをplay関数に追加したところコンパイルエラーになってしまいました。
1 2 3 4 |
$ go run main.go / # command-line-arguments ./main.go:39:3: n.Walk undefined (type NoiseMaker has no field or method Walk) |
interfaceの要件を満たさない値をplay関数に追加することはできないようです。
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 |
package main import "fmt" // Type1 type Whistle string // MakeSound 1 func (w Whistle) MakeSound() { fmt.Println("Weeeeeee!!!!") } // Type2 type Horn string // MakeSound2 func (h Horn) MakeSound() { fmt.Println("Yahooooooo!!!") } // Type3 type Robot string func (r Robot) MakeSound() { fmt.Println("Roboooooooo!!!") } func (r Robot) Walk() { fmt.Println("Walking is a lot of fun!!!") } // Declare Interface type NoiseMaker interface { MakeSound() } func play(n NoiseMaker) { n.MakeSound() } func main() { play(Robot("I'm a Robot")) } |
1 2 |
$ go run main.go Roboooooooo!!! |
やってみよう
では、序章のプログラムを修正してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package main import "github.com/hoge/gadget" type Player interface { Play(string) Stop() } func playList(p Player, songs []string) { for _, song := range songs { p.Play(song) } p.Stop() } func main() { var player Player = gadget.TapePlayer{} mixtape := []string{"Jessie's Girl", "Whip It", "9to5"} playList(player, mixtape) player = gadget.TapeRecorder{} playList(player, mixtape) } |
main関数にinterfaceを実装して、playListの第一引数に渡しました。
プログラムを実行してみましょう。
1 2 3 4 5 6 7 8 9 10 |
$ go run main.go Playing Jessie's Girl Playing Whip It Playing 9to5 Stopped! Playing Jessie's Girl Playing Whip It Playing 9to5 Stopped! |
Type assertions
サンプルコードのTapeRecorderには、Recordメソッドが存在します。これは、TapePlayerには存在しないメソッドです。
前述の通り、interfaceの要件を満たしていないメソッドをinterface typeを引数にした関数内に定義するとエラーになります。
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 |
package main import "github.com/hoge/gadget" type Player interface { Play(string) Stop() } func playList(p Player, songs []string) { for _, song := range songs { p.Play(song) } p.Stop() } func TryOut(p Player) { p.Play("Test Track") p.Stop() p.Record() // error } func main() { TryOut(gadget.TapeRecorder{}) } |
1 2 3 4 |
$ go run main.go # command-line-arguments ./main.go:20:3: p.Record undefined (type Player has no field or method Record) |
このエラーを回避するにはどうすればよいのでしょうか?
最初に考えられる回避策は、型変換でしょうか。
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 |
package main import "github.com/hoge/gadget" type Player interface { Play(string) Stop() } func playList(p Player, songs []string) { for _, song := range songs { p.Play(song) } p.Stop() } func TryOut(p Player) { p.Play("Test Track") p.Stop() recorder := gadget.TapeRecorder(p) // fix recorder.Record() } func main() { TryOut(gadget.TapeRecorder{}) } |
PlayerをTapeRecorderに型変換しようとしました。しかし、interface typeの型変換はできないため、エラーになります。
1 2 3 4 |
$ go run main.go # command-line-arguments ./main.go:20:33: cannot convert p (type Player) to type gadget.TapeRecorder: need type assertion |
このエラー上に、”type assertion“が出てきました。
type assertionは、日本語でいうと「型推論」です。次のように実装するとエラーを回避することができます。
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 |
package main import "github.com/hoge/gadget" type Player interface { Play(string) Stop() } func playList(p Player, songs []string) { for _, song := range songs { p.Play(song) } p.Stop() } func TryOut(p Player) { p.Play("Test Track") p.Stop() recorder := p.(gadget.TapeRecorder) // type assertion recorder.Record() } func main() { TryOut(gadget.TapeRecorder{}) } |
1 2 3 4 5 |
$ go run main.go Playing Test Track Stopped! Recording |
上記の例では、interface typeで宣言したp変数で、TapeRecorderをtype assertionしています。このようにすれば、interfaceに定義されていないメソッドであっても呼び出し可能です。
しかし、この実装は別のバグを発生させます。
例えば、TryOut関数にTapePlayerを渡してみましょう。
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 |
package main import "github.com/hoge/gadget" type Player interface { Play(string) Stop() } func playList(p Player, songs []string) { for _, song := range songs { p.Play(song) } p.Stop() } func TryOut(p Player) { p.Play("Test Track") p.Stop() recorder := p.(gadget.TapeRecorder) recorder.Record() } func main() { TryOut(gadget.TapeRecorder{}) TryOut(gadget.TapePlayer{}) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ go run main.go Playing Test Track Stopped! Recording Playing Test Track Stopped! panic: interface conversion: main.Player is gadget.TapePlayer, not gadget.TapeRecorder goroutine 1 [running]: main.TryOut(0x10e0920, 0xc000084040) /Users/hoge/go/src/github.com/hoge/main.go:20 +0xf4 main.main() /Users/hoge/go/src/github.com/hoge/main.go:26 +0x74 exit status 2 |
このように、type assertionしたinterface typeとは別のinterface typeを渡すとpanicが発生します。
このエラーを回避するには、Go言語では次のように実装します。
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 |
package main import ( "fmt" "github.com/hoge/gadget" ) type Player interface { Play(string) Stop() } func playList(p Player, songs []string) { for _, song := range songs { p.Play(song) } p.Stop() } func TryOut(p Player) { p.Play("Test Track") p.Stop() recorder, ok := p.(gadget.TapeRecorder) if ok { recorder.Record() } else { fmt.Println("Player was not a TapeRecorder") } } func main() { TryOut(gadget.TapeRecorder{}) TryOut(gadget.TapePlayer{}) } |
type assertionの2番目の引数にて、bool値を取得することができます。
type assertionに成功した場合はtrueをそれ以外はfalseを返すので、これをok変数に格納することで、panicを回避します。
1 2 3 4 5 6 7 8 |
$ go run main.go Playing Test Track Stopped! Recording Playing Test Track Stopped! Player was not a TapeRecorder |
Error Inteface
これまで、Go言語でエラーを出力するときは、次のように実装していました。
1 2 3 4 5 6 7 8 9 |
package main import "fmt" func main() { err := fmt.Errorf("Error occurs!") fmt.Println(err.Error()) fmt.Println(err) } |
1 2 3 4 |
$ go run main.go Error occurs! Error occurs! |
Go言語では、error interfaceが次のように定義されています。
1 2 3 |
type error interface { Error() string } |
error interfaceの要件を満たすには、Error関数とstring typeの戻り値をもつメソッドを定義すればOKです。
サンプルコードを示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package main import "fmt" type GetTitleError string func (c GetTitleError) Error() string { // Satisfy the error interface return string(c) } func main() { var err error // GetTitleError satisfies the error interface, so we can assign a GetTitleError to the variable err = GetTitleError("Oh, I'm looking for Harry Potter. But, I can not find it!!!") fmt.Println(err) } |
GetTitleErrorはerror interfaceの要件を満たしたので、error interfaceをtypeとして持つ変数に格納できます。
1 2 3 |
$ go run main.go Oh, I'm looking for Harry Potter. But, I can not find it!!! |
より詳細な情報を持たせたい場合は、次のように実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package main import "fmt" type GetBookNumError int func (g GetBookNumError) Error() string { return fmt.Sprintf("Books are %d!", g) } func main() { var err error err = GetBookNumError(10) fmt.Println(err) } |
1 2 3 |
$ go run main.go Books are 10! |
error 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 |
package main import ( "fmt" "log" ) type GetTitleError string func (c GetTitleError) Error() string { return string(c) } type GetBookNumError int func (g GetBookNumError) Error() string { return fmt.Sprintf("Books are %d!", g) } func canBuyBook(num int) error { if num > 10 { return GetBookNumError(num) } return nil } func main() { var err error = canBuyBook(11) if err != nil { log.Fatal(err) } } |
1 2 3 4 |
$ go run main.go 2019/10/27 21:38:40 Books are 11! exit status 1 |
カスタムエラーを作っているような感覚ですね。
The Stringer Interface
Go言語のfmtパッケージには、Stringer Interfaceが組み込まれています。
1 2 3 4 |
// Stringer Interface type Stringer interface { String() string } |
Stringer Interfaceは、String関数とstringの戻り値を保持しています。
例えば、こんな使い方ができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package main import "fmt" type BookTitle string func (b BookTitle) String() string { return "Book Title is " + string(b) } func main() { bookTitle := BookTitle("Harry Potter") fmt.Println(bookTitle.String()) } |
1 2 3 |
$ go run main.go Book Title is Harry Potter |
自分でカスタマイズした文字列を自由に表示できるので、結構便利なやつです。
先ほどのサンプルでは、「bookTitle.String()」と指定しましたが、次のような書き方でもOKです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package main import "fmt" type BookTitle string func (b BookTitle) String() string { return "Book Title is " + string(b) } func main() { bookTitle := BookTitle("Harry Potter") fmt.Println(bookTitle) fmt.Printf("%s\n", bookTitle) } |
1 2 3 4 |
$ go run main.go Book Title is Harry Potter Book Title is Harry Potter |
色々と応用ができそうです。
The empty interface
fmt.Printlnは、大変便利ですよね。様々なtypeを受け取り、コンソール上に出力できるのですから。
1 2 3 4 5 6 7 |
package main import "fmt" func main() { fmt.Println(1, "1", true) } |
1 2 3 |
$ go run main.go 1 1 true |
Printlnの引数を見てみると「…interface{}」を持っています。これは一体なんなのでしょうか?
1 2 3 4 5 6 7 |
$ go doc fmt Println func Println(a ...interface{}) (n int, err error) Println formats using the default formats for its operands and writes to standard output. Spaces are always added between operands and a newline is appended. It returns the number of bytes written and any write error encountered. |
「…」は、以前学習した可変長引数ですね。これを設定しておくと、呼び出し先で受け取れる引数の数に制限が無くなります。
interfaceを宣言するには、interfaceのメソッドを全て実装したDefined Typeを用意する必要があります。
例えば、次のinterfaceが存在していた場合、MakeBookをメソッドとして定義したDefined Typeが必要です。
1 2 3 4 5 6 7 8 9 10 11 12 |
// interface type BookMaker interface { MakeBook() } // Defined Typeの宣言 type DefinedType string // interfaceの要件を満たす func (d DefinedTYpe) MakeBook { fmt.Println("hoge") } |
しかし、メソッドを定義していないinterfaceを宣言した場合は、どうなるのでしょうか?例えば、こんな感じのやつです。
1 |
type Anything interface {} |
このinterfaceはメソッドを実装していないため、「empty interface」と呼ばれています。
このempty interfaceは、任意の値を受け取ることができます。何の値も持っていないからどのような値も受け入れることができる、寛大なinterfaceです。
つまり、こんな感じの使い方が可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package main import "fmt" func AllAccept(all interface{}) {} func doSomething() { fmt.Println("Do something!") } func main() { AllAccept(12345) AllAccept("Hello") AllAccept(true) AllAccept(doSomething) } |
empty interfaceは、メソッドの実装も求められない代わりに、任意のtypeの値を受け取ることができるのです。
しかし、便利だからといって全ての関数にempty intefaceを実装するのはよくありません。
次のサンプルコードを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package main import "fmt" func AllAccept(all interface{}) { fmt.Println(all) } func doSomething() { fmt.Println("Do something!") } func main() { AllAccept(12345) AllAccept("Hello") } |
1 2 3 |
$ go run main.go 12345 Hello |
前述の通り、どのようなtypeの値でも受け取ることができていますね。
しかし、empty methodは、メソッドを保有していないため、メソッドを呼ぶことはできません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package main import "fmt" func AllAccept(all interface{}) { fmt.Println(all) all.doSomething() // Call method } func doSomething() { fmt.Println("Do something!") } func main() { AllAccept(12345) AllAccept("Hello") } |
1 2 3 4 |
$ go run main.go # command-line-arguments ./main.go:7:5: all.doSomething undefined (type interface {} is interface with no methods) |
この場合は、type assertionをする必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package main import "fmt" type MyType string func (m MyType) doSomething() { fmt.Println("Do something!") } func AllAccept(all interface{}) { fmt.Println(all) myint, ok := all.(MyType) if ok { myint.doSomething() } } func main() { AllAccept(MyType("My Type")) } |
1 2 3 |
$ go run main.go My Type Do something! |
type assertionを行うことでエラーを回避することは可能ですが、通常は次のように実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package main import "fmt" type MyType string func (m MyType) doSomething() { fmt.Println("Do something!") } func AllAccept(m MyType) { fmt.Println(m) m.doSomething() } func main() { AllAccept(MyType("My Type")) } |
1 2 3 4 |
$ go run main.go My Type Do something! |
まとめ

最後にこの章で学んだことをまとめます。
・ interfaceには、期待された値を持つメソッドを定義する
・ interfaceの要件を満たすには、interfaceのメソッドを全て実装する必要がある
・ interfaceはabstract typeであり、メソッドの処理を書くことはできない
・ Go言語ではinterfaceの要件が満たされたかどうかは自動的に判断される
・ interface typeを持つ変数はinterfaceに定義されているメソッドしか呼べない
・ しかし、type assertionを使用すれば、個別に実装したメソッドを呼ぶことができる
・ type assertionは、2番目の戻り値に型変換が成功したか否かを表すbool値を返す
次回
次は、エラーハンドリングについて学びましょう。
コメントを残す
コメントを投稿するにはログインしてください。