feature image

2023年6月13日 | ブログ記事

Goのinterfaceを理解しよう!

こんにちは、最近競プロが少しアツくなっているlogicaです。
先人が少ないけどGoで競プロをやるぞ!って言って、AtCoderの典型90問の簡単めなやつをやりながら、I/Oや二分探索、Union-Find木などをUtility化しています。

さて、Goを学んでいる人の中でかなりの人がつまずく部分、一番の代表はinterfaceだと思っています。
struct / sliceあたりまでは割と直感的に動きますが、interfaceがいきなり機能・挙動共に直感的じゃなくなるんですよね。
それもそのはず、interfaceはそもそも「間接的に型を扱う」ためのものなのですから、直感的に理解できるはずはないのです。

今回は、毎年traPで行われている「Webエンジニアになろう講習会」という講習会の中で出た、「interfaceとは何か」という質問に対する僕の回答をブログ化しました。
質問をしてくれた後輩には好評をいただきましたので、他の人の助けにもなれば幸いです。

なお画像がZennのOGPなのは、作るのがめんどくさかったからです。許して下さい。

また、traPプログでは過去にもinterfaceを解説した記事が出ていますので、良ければそちらもご覧ください。

Go(golang)のinterfaceを†完全理解†した
この記事はtraP Advent Calendar 2021 27日目の記事です。 この記事は「interface何も分からん」ってなっている人が、ざっくりinterfaceの気持ちを理解するために書かれています。よって正確性に欠けることがあります。 目次 1.はじめに 2.例 3.コードの解説 4.実行の流れ 5.この例を踏まえて 6.疑問 7.結論 あなたは誰? : いろり(@irori)。sysAd班,グラフィック班 できること : golang,mysql(諸説あり) できないこと : フロントエンド全般 はじめに golangのコードを読むとちょくちょくイ…

今回の記事の対象読者

A Tour of GoのBasicsまで終わったGoの学習者で、Methods and interfacesがよく理解できないという人
(interface以外の型や関数に関しては理解し、コードが読める事を前提とします)

また、説明をわかりやすくするために厳密な定義を用いていない部分がありますので、プロGopherの方々はどうか温かい目でご覧下さい。

「変数の形式」だというのはもう学ばれたことかと思います。
今回覚えてほしいことは、「型はメソッドを持つことができる」ということです。

メソッド

メソッドは、型が持つ関数のことで、{型}.{メソッド名}()という形で呼び出すことができる特殊な関数です。

メソッドが欲しい状況

例えば、以下のようなstructの型があるとします。

type Blog struct {
  title   string
  content string
}

タイトルと中身の文章を持っただけの、ブログ記事を表す簡単な型です。

今、この記事を表示するために「Blog型を受け取って、中身のtitlecontentを区切り線を介して繋げた文字列を出力する」関数(GetFullArticle())が欲しいとします。
この関数は、普通の関数として以下のように書けますね。

func GetFullArticle(b Blog) string {
  return b.title + "\n" + "------------" + "\n" + b.content
}

ですが、これを単体の関数として定義するよりは、「Blog型自体が、中身のtitlecontentを区切り線を介して繋げた文字列を出力する機能を持つ」としたいと思いませんか?」

メソッドを使う

この時に使えるのがメソッドです。次のように書きます。

func (b Blog) GetFullArticle() string {
  return b.title + "\n" + "------------" + "\n" + b.content
}

このように定義すると、Blog型のbという変数があるとき、GetFullArticle(b)(だけ)ではなくb.GetFullArticle()という形でメソッドを呼び出すことができます。

型に付いた機能として関数を定義する」、これがメソッドの本質だと言っていいと思います(人により様々な解釈があります)。

interface

ようやく本題、interfaceの説明に入りましょう。
interfaceは、「同じメソッド(機能)を持つ複数の型を、ひとくくりにして扱うための仕組み」です。

interfaceが欲しい状況

さあ、先ほどの例を使いましょう。先程のBlog型と、その機能であるGetFullArticle()メソッドを再掲します。

type Blog struct {
  title   string
  content string
}

func (b Blog) GetFullArticle() string {
  return b.title + "\n" + "------------" + "\n" + b.content
}

今、このBlog型の亜種としてBlog2型を定義します。

type Blog2 struct {
  title     string
  paragraph []string
}

記事の中身を、段落のまとまりとして持つような型としました。

このBlog2型に、先ほどのBlog型と同じく、「中身のtitleとcontent(ここでは記事の中身)を区切り線を介して繋げた文字列を出力する」という機能を持たせたいと思います。

func (b Blog2) GetFullArticle() string {
  article := b.title + "\n" + "------------" + "\n"

  for _, paragraph := range b.paragraph {
    article += paragraph + "\n\n" // 段落の間は2重に改行する
  }

  return article
}

さてここで、Blog型とBlog2型について、その型の変数を受け取ってGetFullArticle()で出力された文字列をfmt.Println()で出力する関数をそれぞれ定義したいとします。

func DisplayBlog(b Blog) {
  fmt.Println(b.GetFullArticle())
}

func DisplayBlog2(b Blog2) {
  fmt.Println(b.GetFullArticle())
}

中身が全く同じであることに気づいたでしょうか。
そうなんです。同じ機能を持つ複数の型について、その機能を使う中身が全く同じ関数がいっぱいできることがあるのです。
これではコードがいっぱいになって読みにくいし、同じ機能を持つ型がまた増えた時、関数をその都度定義しなきゃいけなくなります。

どうにかして、これらの「同じ機能を持つ型をひとくくりにして扱えないかな...

interfaceを使う

そこで、満を持してinterfaceの出番です!
interfaceは、「これらのメソッド(機能)を持っている型ならなんでも入っていいよ!」という特別な型です。

type BlogInterface interface {
  GetFullArticle() string
}

このように定義することで、BlogInterfaceという型を指定した部分(引数構造体のフィールドなど)は「GetFullArticle()という、引数を取らずstring型を出力するメソッドがある型は、何でも入っていいよ」という状態になります。
同じメソッドを持つ型を、ひとくくりにして扱えるわけです。

このinterfaceを使って、先ほどのDisplay~~関数をスッキリさせてみましょう。

func DisplayBlog(b BlogInterface) {
  fmt.Println(b.GetFullArticle())
}

ドーン。終了です。
これでBlog型のbという変数も、Blog2型のb2という変数も

DisplayBlog(b)
DisplayBlog(b2)

という風にDisplayBlog()関数に渡すことができます。
interfaceが、同じメソッドを持つ複数の型をひとくくりにして扱えるという意味が分かったでしょうか?

interfaceの注意点

1つ気をつけなければいけないことは、interface型の変数を使う時メソッドしか使えなくなり、そこに当てはまる構造体のフィールドなどは使えなくなります。

例えば、DisplayBlog()関数で、titleが無い記事は出力したくないと思ったとき、

func DisplayBlog(b BlogInterface) {
  if b.title == "" { // b.titleはアウト
    return
  }

  fmt.Println(b.GetFullArticle())
}

という書き方はできません。
interfaceは「ここに入る型は、このメソッドを持っているよ」ということしか保証してくれないからです。
別の言い方をすると、interfaceは単体としてみた時、メソッドしか持たない型であると言えます。

もしこのようにtitleを使いたい場合、GetTitle()のようなタイトルを取得するだけのメソッドをBlogInterfaceの定義に追加して、BlogInterfaceでひとくくりにされた型全てにこのメソッドを用意する必要があります。

余談

なぜ「interface」と呼ばれるのか

interfaceの言葉の意味を少し掘り下げてみましょう。
(以下はあくまで僕の理解です)

interfaceの和訳は「接触面」、大まかに言うと「間に挟まるもの」という意味の言葉です。
Goでは「interfaceでない型」と「それを使うもの(構造体関数)」の間に挟まるのでそのような呼び方をされます。
(Go以前のプログラミング言語からある概念なので、厳密ではないです)

type Type1 struct {~}
type Type2 int
type Type3 []string   // 全て、下記のinterfaceで指定されたメソッドを持つ

↓ ひとまとめにする

type Inter interface {~}

↓ 以下のように使える

func FuncX (i Inter) {~}
type TypeX struct {
  i Inter
}

上の図のように、「間に挟まっている」イメージが持てると思います。

interfaceのメリット

interfaceを使うと

などの様々なメリットがありますが、これは学習を進めていくにつれて追々感じていくでしょう。

io.Reader

最後に、interfaceの代表例と言えるio.Reader型を紹介したいと思います。

標準パッケージのioには、io.Readerという型があります。
以下のように定義されます。

type Reader interface {
  Read(p []byte) (n int, err error)
}

Read()という、「何かしらの内容を受け取ったpという変数の中に格納し、格納したバイト数と起こったエラーを出力する」機能を持った型をひとくくりにするためのinterfaceです。

io.Readerは、指定された機能の少なさから、様々な型をひとまとめにすることができます。例えば

これらの様々な型を、「とりあえず中身を読み出して何かに使う」ような関数で受け取るとき、io.Readerとしてひとくくりにできることがどれだけ有用かは想像できると思います。
例えばRead()した中身を出力する関数であったり、その文字数をカウントするような関数であったりです。

io.Readerに対する愛はこちらの記事に勝るものが無いので、io.Readerに興味が湧いた方は読んでみると良いと思います。

io.Readerをすこれ - Qiita
0. なんで io.Reader?去年のアドベントカレンダーでは Go4 までだったのが今年はなんと Go7 までできており、Go への関心が高まっているのはいち Go 好きとしてはうれしい限りです。Go の良さは色々なところにあ...

終わりに

いかがでした?わかりやすかったですか?
Goをこれからバリバリ使っていきたい!という方々が、スムーズに学習を進める手助けとなれたなら幸いです。

traPで数年間にわたり開発が進められ、今僕がメンテナーをしている、traQというOSSのメッセージングサービスがあります。
その中身のGoコードでは多数のinterfaceが使用され、複数人で開発するのに適した構造のコードが組み上げられています。
もし「interfaceの実用例がもっと知りたい!」という方は、頑張って読解を進めてみると必ず力になると思います。

https://github.com/traPtitech/traQ

それでは夜も遅いので、キーボードから手を放そうと思います。
またどこかでお会いしましょう。

logica icon
この記事を書いた人
logica

20B。何でも屋です。SysAd / CTF / アルゴリズム / サウンド / グラフィック(デザイン部) にいます。

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2021年8月12日
CPCTFを支えたWebshell
mazrean icon mazrean
2021年5月19日
CPCTF2021を実現させたスコアサーバー
xxpoxx icon xxpoxx
2024年3月15日
個人開発として2週間でWebサービスを作ってみた話 〜「LABEL」の紹介〜
Natsuki icon Natsuki
2023年10月20日
DIGI-CON HACKATHON 参加記事「Comic DoQ」
mehm8128 icon mehm8128
2023年6月23日
2023 春ハッカソン 26班 『traP Mission』
Ras icon Ras
2023年3月13日
GoでWebSocketのテスト書く
Ras icon Ras
記事一覧 タグ一覧 Google アナリティクスについて 特定商取引法に基づく表記