【Go言語入門】10.インターフェースについてさくっと学ぶ

今回はインターフェースについて学びます。使い勝手がわからないという人はなんとなくでも雰囲気を掴められればと思います。

前回の記事

定義

TypeScriptではカスタムタイプの定義に愛用しておりましたが、Goでは変数とメソッドのシグネチャの集合体である構造体と比べるとインターフェースはメソッドのシグネチャの集合体です。変数は含まれません。

これを構造体とうまく組み合わせていくことでまとまりの単位をある程度明確化でき、オブジェクト指向っぽく設計することができます。(と、いうのが今の所の解釈です。)

使い方

使い方自体はシンプルで、interfaceキーワードを使って定義をし、宣言した後で構造体に初期化することで実装できます。

package main

import "fmt"

type Manager interface {
  UpdateName(string)
  UpdateDescription(string)
}

func (u *User) UpdateName(s string) {
  u.Name = s
}

func (u *User) UpdateDescription(s string) {
  u.Description = s
}

type User struct {
  Id            string
  Name          string
  Description   string
  FollowingIds  []string
  FolloweredIds []string
}

func (u1 *User) Follow(u2 *User) {
  u1.FollowingIds = append(u1.FollowingIds, u2.Id)
  u2.FolloweredIds = append(u2.FolloweredIds, u2.Id)
}

func main() {
  var k Manager
  ken := &User{Id: "ken_id", Name: "Ken"}
  k = ken
}

少し長いですが、これでUserManagerを実装していることになります。

さり気なくポインタで宣言しておりますが、ここでの構造体の扱いは基本ポインタ経由で行っております。

丁寧な書き方をしておりますが、interfaceにメソッドを追加することによって実質明示的な宣言は必要なくなります

func main() {
  var k Manager
  ken := &User{Id: "ken_id", Name: "Ken"}
  k = ken
}
↓
func main() {
  var k Manager = &User{Id: "ken_id", Name: "Ken"}
}

このように。

実践での使い方とは異なるかと思いますが、interfaceの役割として少しでも雰囲気を掴めるようにこのような書き方をしております。

型アサーション

何もメソッドを持っていないinterfaceはnilを返します。

package main

import "fmt"

type I interface{}

func main() {
  var i1 I
  var i2 interface{}
  fmt.Println(i1, i2)
}

// >>> <nil> <nil>

またinterface{}は任意の方の値を保持することができます。

TypeScriptでいうとanyに似てる部分がありますね。

package main

import "fmt"

func do(i interface{}) {
  fmt.Printf("value is %v\ntype is %T\n", i, i)
}

func main() {
  do(100)
  do(1.234)
  do(false)
  do("")
}

// >>> value is 100
// >>> type is int
// >>> value is 1.234
// >>> type is float64
// >>> value is false
// >>> type is bool
// >>> value is 
// >>> type is string

今まで怒られていたであろうこういうことができます。

こういった何でも屋的使い方だけではなく、空interfaceが型アサーションが使えます。

package main

import "fmt"

type I interface{}

func main() {
  var i I = 10
  v, ok := i.(int)
  fmt.Println(v, ok)
}

// >>> 10 true

これはインターフェースI変数iが代入時に型intを保持し、基となる値をvに、指定した型intを保持しているか真偽判定した結果をokに代入しております。

okはもちろん省略が可能ですが、その場合、i.(string)とかにするとpanicを引き起こすはずです。

型チェックを目的とした場合、_, ok := i.(型名)などでも十分可能ですが、switchを使うとさらにわかり易くできます。

package main

import "fmt"

type I interface{}

func do(i interface{}) {
  switch v := i.(type) {
  case int:
    fmt.Printf("type of %v is int\n", v)
  case string:
    fmt.Printf("type of %v is string\n", v)
  default:
    fmt.Printf("type of %v is unknown\n", v)
  }
}

func main() {
  do("test")
  do(100)
  do(true)
}

// >>> type of test is string
// >>> type of 100 is int
// >>> type of true is unknown

これらは自主制作などの単独実装や特に未経験の初心者の場合は、なかなかメリットを感じづらかったりするものですが、段階的なアップデートのための機能拡張などを意識すると少しは見えてくるかも知れません。