はじめに
はじめまして。現在Goを学習中のrikutoです。
この記事は、Javaの知識を引きずったままGoのinterfaceを学んで混乱した経験をもとに、自分なりに考えを言語化し、整理するために書いたものです。
同じようにJava経験者でGoを学んでいる方の参考になれば嬉しいです。
もし内容に誤りや改善点などがあれば、コメントで教えていただけると嬉しいです。
JavaとGoのinterface
Javaのinterfaceについて
Javaにおけるinterfaceは、「クラスが実装するもの」 という認識が強いと思います。
class Sample implements Interface {
// 実装
}
-
implementsを明示的に書く - 「このクラスはinterfaceを実装している」と宣言する
そのため、interfaceは、実装する対象であり、関係性をはっきり示すもの というイメージを持っていました。
Goのinterfaceについて
一方、Goのinterfaceは書き方からして大きく異なります。
type sample interface {
method()
}
- Goには
implementsというキーワードが存在しません - struct側でも、「このinterfaceを実装しています」と書くこともありません
Goでは、
interfaceに定義されたメソッドを持っていれば、そのinterfaceを満たしている
という考え方が取られています。
なぜ混乱したのか?
混乱した原因はシンプルで、
「interfaceは実装するものだ」という思い込みでした。
Javaでは、
- interfaceを実装する
- implementsを書く
- 関係性を明示する
という流れが当たり前です
その感覚のままGoのコードを見ると、
- どこでinterfaceを実装しているのかが分からない
- いつinterfaceを満たしたことになるのか分からない
と感じてしまいます。
しかしGoは、
実装しているかどうかを宣言するのではなく、
必要なメソッドを持っているかどうかだけを見る
という思想です。
この違いに気づくまで、
Goのinterfaceが理解できませんでした。
ただ、この違いを明確にしたことで、Goのinterfaceの考え方が整理できました。
実際にGoのinterfaceを書いてみる
①interfaceの宣言
type Printer interface {
Print()
}
「Printできるもの」を表すinterfaceです。
ここではメソッド名のみを宣言しています。
②同じ振る舞い(Print)を持つstructを宣言
type ConsolePrinter struct{}
func (c ConsolePrinter) Print() {
fmt.Println("print to console")
}
type FilePrinter struct{}
func (f FilePrinter) Print() {
fmt.Println("print to file")
}
- どちらも
Print()を持つ - interfaceを意識した記述は一切ない
③interface型を引数に取る関数を宣言
func ExecutePrint(p Printer) {
p.Print()
}
④呼び出し側
func main() {
c := ConsolePrinter{}
f := FilePrinter{}
ExecutePrint(c)
ExecutePrint(f)
}
ExecutePrint関数は、Printerinterface 型を引数に取ります。
しかし、実際に渡しているのは、ConsolePrinterやFilePrinter型の変数です。一見すると、interface 型ではない値を渡しているように見えます。
それでもエラーにならないのは、
どちらの型もPrint()メソッドを持っており、
Printerinterface の条件を満たしているためです。
Goでは、
「この interface を実装しています」と宣言する必要はありません。
必要なメソッドを持っていれば、その時点で interface を満たします。
まとめ : Goのinterfaceは「実装するもの」ではない
Javaを最初に学んだ立場からGoのinterfaceを見ると、
「どこでimplementsしているのか分からない」
「なぜinterface型として扱えるのか分からない」
のように混乱しやすいと感じました。
その原因は、
interfaceは実装するものだ、という思い込みにありました。
Goのinterfaceは、
「このinterfaceを実装しています」と宣言するものではなく、必要なメソッドを持っているかどうかだけで判断されます。
つまりGoのinterfaceは、
クラスの関係性を表すものではなく、
振る舞い(何ができるか)を表すものです。
このように捉えることで、
Goのinterfaceの考え方が理解できました。