#はじめに
Go言語を勉強していて、interface
が絡んでくるとなかなか理解ができなくなってしまうという人に読んでほしいです。
特にTour of GoのStringersあたりでわからなくなった場合を想定しています。
また、Javaと対比して理解を深めることを目的としているため、Javaの経験がある方には刺さる説明になるかと思います。
#なぜわからなくなるのか
原因としては2つあると思います。
- そもそもインターフェースの概念がうまく理解できてない
- 明示的な実装(implement)が前提になっていると勘違いしている
前者の方は、全く種類の違う型(クラス)がインターフェース型として、一部同じものとしてに扱うことができるということを知らない。
後者の方は、Javaの明示的なインターフェースの実装ありきで考えすぎて、Goの書き方に困惑してしまっている。
ということなのだと思います。
#interfaceの実装例(お題:食べるということとは?)
package main
import "fmt"
// 食べるためのインターフェース
type Eater interface{
PutIn() // 口に入れる
Chew() // 噛む
Swallow() // 飲み込む
}
// 人間の構造体
type Human struct{
Height int // 身長
}
// カメの構造体
type Turtle struct{
Kind string // 種類
}
// 人間用のインターフェースの実装
func (h Human) PutIn(){
fmt.Println("道具を使って丁寧に口に運ぶ")
}
func (h Human) Chew(){
fmt.Println("歯でしっかり噛む")
}
func (h Human) Swallow(){
fmt.Println("よく噛んだら飲み込む")
}
// カメ用のインターフェースの実装
func (h Turtle) PutIn(){
fmt.Println("獲物を見つけたら首をすばやく伸ばして噛む")
}
func (h Turtle) Chew(){
fmt.Println("クチバシで噛み砕く")
}
func (h Turtle) Swallow(){
fmt.Println("小さく砕いたら飲み込む")
}
// インターフェースが引数になる、食べるメソッド
func EatAll(e Eater){
e.PutIn() // インターフェースから呼び出す
e.Chew()
e.Swallow()
}
func main() {
var man Human = Human{Height: 300} // 人間用の構造体を作成
var cheloniaMydas = Turtle{Kind: "アオウミガメ"} // カメ用の構造体を作成
var eat Eater // インターフェースEater型の変数を用意
fmt.Println("<人間が食べる>")
eat = man // Human型がインターフェースであるEater型に変換される
EatAll(eat) // インターフェースを引数に関数を呼ぶ
fmt.Println("<カメが食べる>")
eat = cheloniaMydas // Turtle型がインターフェースであるEater型に変換される
EatAll(eat)
}
<人間が食べる>
道具を使って丁寧に口に運ぶ
歯でしっかり噛む
よく噛んだら飲み込む
<カメが食べる>
獲物を見つけたら首をすばやく伸ばして噛む
クチバシで噛み砕く
小さく砕いたら飲み込む
コードがずいぶんと長くなってしまいましたが、きちんと説明するのでついてきてください。
それでは、次の4つのフェーズに分けて紹介していきます。
- インターフェースの定義
- インターフェースを使った関数
- インターフェースの実装
- インターフェースの使い方
インターフェースの定義
// 食べるためのインターフェース
type Eater interface{
PutIn() // 口に入れる
Chew() // 噛む
Swallow() // 飲み込む
}
// 人間の構造体
type Human struct{
Height int // 身長
}
// カメの構造体
type Turtle struct{
Kind string // 種類
}
まず「食べる」ということをプログラムで表現することを考えましょう。
口に入れる、噛む、飲み込むといったフェーズにわけて、関数をインターフェースに定義してみました。
といっても答えは人それぞれですが、今回はこんな感じで作ってみます。
次に、構造体(Javaでいうところのクラスに相当)を定義しました。
人間の構造体とカメの構造体です。ポイントは「それぞれの構造体の中身がまるっきり違ってもいい」ということにあります。
インターフェースを使った関数
// インターフェースが引数になる、食べるメソッド
func EatAll(e Eater){
e.PutIn() // インターフェースから呼び出す
e.Chew()
e.Swallow()
}
今回の最終ゴールは、**「インターフェースを引数にとるEatAll
関数を作ること」**と考えるとわかりやすくなります。
まずその第一歩として、最初にインターフェースを定義したのです。
インターフェースを引数にとるとどんなことがうれしいのでしょうか。
例えば、こうすることで、「飲む」ということをプログラムで表現するときに、さきほどのインターフェースを使い回すことができそうですよね。液体なので噛む必要はなくなります。
// インターフェースが引数になる、飲むメソッド
func DrinkAll(e Eater){
e.PutIn() // インターフェースから呼び出す
e.Swallow()
}
プログラムで書くとこんな感じです。
インターフェースの実装
私が一番混乱したのはここの部分です。
Javaであれば、
class Human implements Eater{
private int height;
public void PutIn(){
System.out.println("道具を使って・・・");
}
// ...
}
こうなるはずです。
決定的にJavaと違う点は、implements
がないことです。ここに注意してください。
Goでは、interfaceの中にある関数名と同一の関数が全て実装されている構造体に自動的に実装されると思ってください。
// 人間用のインターフェースの実装
func (h Human) PutIn(){
fmt.Println("道具を使って丁寧に口に運ぶ")
}
func (h Human) Chew(){
fmt.Println("歯でしっかり噛む")
}
func (h Human) Swallow(){
fmt.Println("よく噛んだら飲み込む")
}
// カメ用のインターフェースの実装
func (h Turtle) PutIn(){
fmt.Println("獲物を見つけたら首をすばやく伸ばして噛む")
}
func (h Turtle) Chew(){
fmt.Println("クチバシで噛み砕く")
}
func (h Turtle) Swallow(){
fmt.Println("小さく砕いたら飲み込む")
}
この部分では、レシーバーを使って、Human
とTurtle
両方にそれぞれの3つのメソッドを定義しています。
試しに、eating.go
からHuman
のSwallow()
を抜くとどうなるでしょうか。
source_file.go:56: cannot use man (type Human) as type Eater in assignment:
Human does not implement Eater (missing Swallow method)
こうなります。
まだmain
関数の中身を説明していないので、わかりづらいかもしれません。
これは、Human
型がインターフェースのEater
型として割り当てることには使えないと言っています。
つまり、Human
型にはインターフェースのEater
型が実装されていないということです。
裏を返せば、インターフェースの中にある同じ名前のメソッドを全て実装するだけで自動的にインターフェースが実装されているということになります。
##インターフェースの使い方
最後に、main
関数の中身を紹介します。
func main() {
var man Human = Human{Height: 300} // 人間用の構造体を作成
var cheloniaMydas = Turtle{Kind: "アオウミガメ"} // カメ用の構造体を作成
var eat Eater // インターフェースEater型の変数を用意
fmt.Println("<人間が食べる>")
eat = man // Human型がインターフェースであるEater型に変換される
EatAll(eat) // インターフェースを引数に関数を呼ぶ
fmt.Println("<カメが食べる>")
eat = cheloniaMydas // Turtle型がインターフェースであるEater型に変換される
EatAll(eat)
}
最初の2行は、構造体を定義してるだけです。
3行目は、インターフェースEater型の変数を定義しています。ここが、ポイントです。あくまでもこの変数eat
はEater
型です。もう一度いいますが、インターフェースですからね。
そして5行目でHuman
型の構造体の実体をインターフェース型の変数に入れています。この時点で、Eater
型に変換されていると思ってください。
そして、6行目では先程説明したEatAll
関数にインターフェースであるeat
を渡しています。
これを同様にカメの場合でやります。
すると、結果が人間の場合とカメの場合で変わってることがわかるかと思います。
これをStringerで考える
Tour of Goに出てくるものをそのまま持ってきました。
このチュートリアルが言っていることというのは、
type Stringer interface {
String() string
}
- そもそも
fmt.Stringer
というインターフェースは上記で定義されている。 -
fmt.Stringer
はfmt
パッケージの中にある関数の引数としてこのインターフェースを使っている。
fmt
パッケージの中にある関数として代表的なものがfmt.Println
です。この引数にもfmt.Stringer
が使えるということです。
それでは、実際に実装してみましょう。
あれ、実装ってどうやりましたっけ?implements
のようなものはありませんし。
そうです。インターフェースにある関数を全て実装したい型に実装してしまえばそれでおしまいです。
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
今回はPerson
に実装したいので・・・
レシーバーにPerson
を、そして、Stringerの中にある関数と同じ名前の関数を作り、引数の型、返り値の型も合わせました!
これで実装が完了というわけです。
流れはわかったでしょうか?
もし、次のExerciseがわからない方は、この記事を御覧ください。ものすごく納得がいく説明でわかりやすいです。