こんにちは。masafumiです。
現職場に転職してきて約半年が経ちました。
GoやPerl、DDD、API実装、テストコードなど日々新しい知識に触れ、まだ分からないことだらけですが、一つ一つ自分の身にできていってるのではと感じます。
最近、新しいプロジェクトが始まり、そこからほとんどGo言語でのコーディングにシフトしましたが、その中でこの Interface というやつが特に最初よく分からなかったので、現状の私なりの理解をまとめたいと思います。
Interfaceとは?
職場の先輩の「まずは公式ドキュメントを見る」という心がけに私も習って、まずはGo言語公式の「A tour of Go」の中でのInterfaceの定義を再確認してみます。
「Interfaceとはメソッドの集合」
Interfaceはメソッドのシグネチャーの集合です。
従って、Interfaceの中に含まれる型は全てメソッドである必要があります。
A tour of Goの中では、例として以下のような Interfaceを作っています。
//Abs()というメソッドを持つ 'Abser' という名のInterface型を用意する
type Abser interface {
Abs() float64
}
type MyFloat float64
// 絶対値を返すメソッド Abs()の実装内容
// Abs()はMyFloat型のためのメソッド
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
これはオブジェクト指向的に言うと、Abs()というメソッドを持った(満たす)Abserというクラスを用意した、ということになります。
従って、Abserから作られたインスタンスは Abs()メソッドを使えることになります。
func main(){
var a Abser
f := MyFloat(-2.5)
// f := -2.5 // 不可
a = f
fmt.Println(a) // -2.5
fmt.Println(f) // -2.5
fmt.Println(a.Abs()) // 2.5
}
ここで、 f := -2.5
とした値をAbserインターフェース型の変数 a
に代入することはできません。
なぜなら、Abserインターフェース型に集められたメソッド(Abs())は、MyFloat型のためのメソッドであるため、それ以外の型を代入することはできないためです。
Interface と interface{}は違うみたい
var i interface{}
変数宣言の時に使われているこの interface{}
は何だ?と思ってました。
チュートリアルによると、これは
The interface type that specifies zero methods is known as the empty interface:(メソッドが一つもないinterfaceは "空のインターフェース"という)
らしいです。
メソッドが無いからそのメソッドの型に縛られない。
だから interface{} 型で宣言すれば、どんな型でも持てるということになるんですね。
Interfaceは何が便利なのか?
Interfaceの良いところとして、
- 実装と使用を切り離して考えられる
- 依存性の逆転ができる
とよく言われます。
私も最初はよく分かりませんでしたが、最近少し分かってきた部分があります。
実際の開発を想定すると、Interfaceとその実装を作るパッケージ、実際に使うパッケージは分かれることになると思います。
ペペロンチーノを作ってほしい!作り方は別に何でもいい!美味しければ!
実装と使用を切り離して考えられるという点は、私も先輩に教えてもらってとても理解しやすかったです。
パッと浮かんだ例として、ペペロンチーノの作り方を例に書いてみました。
- main.go : ペペロンチーノを作ってもらう側
- food.go : ペペロンチーノを作る側
各ファイルは上記のような役割のイメージです。
Interfaceとは結局メソッドの塊であり、使ってもらってナンボです。
従って、メソッドを使ってもらう側、つまりペペロンチーノ作成を依頼される food.go 側でペペロンチーノを作るというInterfaceを実装し公開する(先頭を大文字にして)べきであると考えました。
food.go側で実際のペペロンチーノの作り方のメソッド Cooking()
を実装しました。こちらを Peperoncino
インターフェースを通して公開します。
main.go側では特にペペロンチーノの作り方は知りません。でもとにかく作ってもらえればいい、美味しければいいのでこのメソッドを使って作ってもらうことができました。
これこそが、実装と使用を切り離して考えられるという、Interfaceのメリットなのだと思います。
package main
import (
"fmt"
"food"
)
func main() {
var p Peperoncino
var chef Chef
p = chef
// ペペロンチーノを作ってもらう。ただし、こちらからはどう作られるのかは知らない。とにかく作ってもらえれば良い。
fmt.Println(p.Cooking())
}
package food
type Peperoncino interface {
Cooking() string
}
type Chef string
// ペペロンチーノの具体的な作り方を実装している
type (c Chef) Cooking() string {
pasta := 1
olive_oil := 1
onion := 1
chili := 1
bacon := 1
return "Done!!
}
他にも、私が思うInterfaceのメリットは、実装と使用を切り離せることによって、実装がまだ未完成でも、使用する側でとにかく開発を進められるというところです。
上記の例で言うと、 Cooking()
メソッドは、最悪引数と返り値だけでも書けていれば使用する側で使用できます。
アプリケーションの開発の自由度が上がるという点でも、Interfaceは便利なものだと感じられます。