こんにちは。KOUKIです。
とあるWeb系企業でエンジニアをやってます。
本記事では、Go言語のInterfaceについて初級者から中級者に上がるために知っておいた方がいいことを記載しました。
Interfaceの理解に少しでも役立ったら幸いです。
<目次>
Interfaceの基本
Interffaceの基本は、以下を参考にしてください。
Interfaceを使わないバージョン
最初にInterfaceを使わないコードを書いてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package main import "fmt" type person struct { first string } func (p person) speak() { fmt.Println("from a person - this is my name", p.first) } func main() { p1 := person{ first: "selfnote", } fmt.Printf("%T\n", p1) p1.speak() } |
person構造体を用意し、speakメソッドを定義しました。
そして、main関数からインスタンス化して、処理を呼び出します。
1 2 3 |
$ go run main.go main.person from a person - this is my name selfnote |
簡単ですね^^
Interfaceを使ったバージョン
次に、Interfaceを使ったバージョンを書いてみましょう。
Interfaceを定義
最初に、Interfaceを定義します。
1 2 3 |
type human interface { speak() } |
Go言語の場合は、interfaceキーワードでinterfaceを定義します。
Interfaceを呼び出す
続いて、main関数から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 |
package main import "fmt" type person struct { first string } func (p person) speak() { fmt.Println("from a person - this is my name", p.first) } type human interface { speak() } func main() { p1 := person{ first: "selfnote", } fmt.Printf("%T\n", p1) // interfaceを型に指定する var selfnote human selfnote = p1 selfnote.speak() fmt.Printf("%T\n", selfnote) } |
human interfaceを型にしたselfnote変数を定義しました。
その後に、person構造体からインスタンス化したp1を代入してますが、このコード、コンパイルエラーになると思いますか?
実はコンパイルエラーになりません。
1 2 3 4 |
$ go run main.go main.person from a person - this is my name selfnote main.person |
Go言語では、Interfaceに定義した関数(ここではspeak)と同名のメソッドを定義した構造体は、Interfaceを実装したとみなされます。
person構造体は、speakメソッドを実装しているため、human interfaceを実装したと見なされます。よって、selfnote変数にp1変数を代入することが可能になります。
もちろん、speakメソッドをコメントアウトすればエラーになります。

1 2 |
var p1 person cannot use p1 (variable of type person) as human value in assignment: missing method speak compiler(InvalidIfaceAssign) |
面白いですね!
Interfaceメソッドの呼び出し
次に、person 構造体を持った構造体を定義しましょう。
1 2 3 4 5 6 7 8 |
type superman struct { person ltk bool } func (sm superman) speak() { fmt.Println("I'm a superman - this is my name", sm.first) } |
このコードは、下記のように呼び出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
func main() { p1 := person{ first: "selfnote", } sm1 := superman{ person: person{ first: "Super selfnote", }, ltk: true, } fmt.Printf("%T\n", p1) // interfaceを型に指定する var selfnote, superSelfnote human selfnote = p1 superSelfnote = sm1 selfnote.speak() superSelfnote.speak() } |
selfnote, superSelfnoteをhuman interfaceの型で定義し、それぞれにperson構造体、superman構造体のインスタンスを代入しています。
一度、このソースコードを実行してみましょう。
1 2 3 4 |
$ go run main.go main.person from a person - this is my name selfnote I'm a superman - this is my name Super selfnote |
想定通り、それぞれのメソッドの処理が呼ばれましたね。
定義した型は同じhumanなのに、それぞれのメソッドが呼ばれるのはなんだか不思議な感じがしますね。
応用: interface型の引数
ここでInterfaceの応用的な使い方をみていきましょう。
下記の関数を定義します。
1 2 3 |
func talk(h human) { h.speak() } |
この関数は、human 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 |
func main() { p1 := person{ first: "selfnote", } sm1 := superman{ person: person{ first: "Super selfnote", }, ltk: true, } fmt.Printf("%T\n", p1) // interfaceを型に指定する var selfnote, superSelfnote human selfnote = p1 superSelfnote = sm1 selfnote.speak() superSelfnote.speak() fmt.Println("-----------------------------") talk(p1) talk(sm1) talk(selfnote) talk(superSelfnote) } |
human interfaceを満たす変数は全てtalk関数に渡せました。
1 2 3 4 5 6 7 8 9 |
$ go run main.go main.person from a person - this is my name selfnote I'm a superman - this is my name Super selfnote ----------------------------- from a person - this is my name selfnote I'm a superman - this is my name Super selfnote from a person - this is my name selfnote I'm a superman - this is my name Super selfnote |
逆にいうとhuman interfaceを満たさない変数は渡せないので、speakメソッドを実装した変数のみ引数に指定できる、という制約を作ることができます。
アクセサーを作る
続いて、Interfaceを使ってアクセサーを作成しましょう。
シチュエーション的には、2つのDBが存在するとします。
- MongoDB
- PostgreSQL
このDBはそれぞれ「save」、「retrieve」というメソッドを持ちDB操作を行います。
そこで、accessor 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 53 54 55 56 57 58 59 60 61 62 63 64 |
package main import "fmt" type person struct { first string } // DBを定義 type mongo map[int]person type postgres map[int]person // DB操作メソッド func (m mongo) save(n int, p person) { m[n] = p } func (m mongo) retrieve(n int) person { return m[n] } func (pg postgres) save(n int, p person) { pg[n] = p } func (pg postgres) retrieve(n int) person { return pg[n] } // アクセサー type accessor interface { save(n int, p person) retrieve(n int) person } // putを介して、DBのsaveメソッドにアクセス func put(a accessor, n int, p person) { a.save(n, p) } // getを介して、DBのretrieveメソッドにアクセス func get(a accessor, n int) person { return a.retrieve(n) } func main() { dbm := mongo{} dbp := postgres{} p1 := person{ first: "Selfnote", } p2 := person{ first: "Super Selfnote", } put(dbm, 1, p1) put(dbm, 2, p2) fmt.Println(get(dbm, 1)) fmt.Println(get(dbm, 2)) put(dbp, 1, p1) put(dbp, 2, p2) fmt.Println(get(dbp, 1)) fmt.Println(get(dbp, 2)) } |
前述した通り、関数の型にinterface型を指定するとそのinterfaceのメソッドを実装した構造体の変数しか引数として渡せません。
この性質を利用するとアクセサーを作ることができます。
アクセサーを介してでないとDBの操作を行えない、などの制約が作れそうですね^^
1 2 3 4 5 |
$ go run main.go {Selfnote} {Super Selfnote} {Selfnote} {Super Selfnote} |
アクセサーをカプセル化する
もう少し、アクセサーを改良してみましょう。
先ほど実装したアクセサーをpersonService構造体でカプセル化(カプセル化という用語でいいのか?)します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// アクセサーをカプセル化 type personService struct { a accessor } func (ps personService) get(n int) (person, error) { p := ps.a.retrieve(n) if p.first == "" { return person{}, fmt.Errorf("No person with n of %d", n) } return p, nil } |
このコードは、以下のように使います。
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 |
unc main() { dbm := mongo{} dbp := postgres{} p1 := person{ first: "Selfnote", } p2 := person{ first: "Super Selfnote", } ps := personService{ a: dbm, } ps2 := personService{ a: dbp, } put(dbm, 1, p1) put(dbm, 2, p2) fmt.Println(get(dbm, 1)) fmt.Println(get(dbm, 2)) put(dbp, 1, p1) put(dbp, 2, p2) fmt.Println(get(dbp, 1)) fmt.Println(get(dbp, 2)) // ok fmt.Println(ps.get(1)) fmt.Println(ps.get(1)) // ng fmt.Println(ps2.get(3)) fmt.Println(ps2.get(3)) } |
personServiceをインスタンス化するとき、dbm(MongoDB),dbp(Postgres)を渡してますね。
これにより、処理の呼び出し時にDB操作メソッドをインスタンスのメソッドとして呼び出せます。
1 2 3 4 5 |
// カプセル化前 get(dbp, 1) // カプセル化後 ps.get(1) |
処理の呼び出しがよりシンプルになりましたね!
1 2 3 4 5 6 7 8 9 |
$ go run main.go {Selfnote} {Super Selfnote} {Selfnote} {Super Selfnote} {Selfnote} <nil> {Selfnote} <nil> {} No person with n of 3 {} No person with n of 3 |
Interfaceの使用例
Go言語の標準パッケージでもInterfaceはよく使われます。
例えば、ioパッケージのReaderが代表ですね。
1 2 3 |
type Reader interface { Read(p []byte) (n int, err error) } |
このReader interfaceは至る所で使われます。
例えば、osパッケージのCopyメソッドなどがそうです。
1 2 3 4 5 |
func Copy(dst Writer, src Reader) (written int64, err error) func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) func CopyN(dst Writer, src Reader, n int64) (written int64, err error) |
Go言語の標準パッケージを眺めてみて、Interfaceの使われ方を学ぶことも有益だと思います^^
まとめ
今回紹介したInterfaceの機能はごく一部です。
Interfaceは汎用性が高いので様々な用途で使われます。
以下の記事でおすすめの使い方を紹介しているので、ぜひご一読ください。
コメントを残す
コメントを投稿するにはログインしてください。