プログラミング
記事内に商品プロモーションを含む場合があります

Go言語のメソッドとinterfaceの使い方、interface{}との違い

Aru

Goのメソッドは構造体(型)に関連づけられた関数でシンプルながらも柔軟な設計が可能です。interfaceは特定のメソッドを持つ型を抽象化して扱える仕組みで、暗黙的な実装や多態性といった特徴を持ちます。この記事では、Go言語における「メソッド」と「interface」について、サンプルプログラムを交えながらわかりやすく解説します。

Go言語のメソッド

メソッドとは

メソッドと聞くとオブジェクト指向プログラミングを想像するかと思いますが、Go言語のメソッドは特定の型に関連づけられた関数のことになります。

言葉ではイマイチわかりにくいので、実際に書いてみます。

package main

import "fmt"

type Triangle struct {
	Base   int
	Height int
}

func (t Triangle) Area() int {
	return t.Base * t.Height / 2
}

func main() {
	t := Triangle{Base: 10, Height: 5}
	fmt.Println("area = ", t.Area())
}

上記のプログラムでは底辺(base)と高さ(height)を持つ三角形(Triangle)の型を定義しています。メソッドは、以下の部分になります。

func (t Triangle) Area() int {
	return t.Base * t.Height / 2
}

このように定義することで、Triangle型の関数Areaを定義できます。

(t Triangle)の部分はレシーバーと呼ばれるもので、これにより三角形型のArea関数になります。呼び出す場合は、変数名.Area()とします。

これがGo言語のメソッドです。

メソッドとは直接関係ないですが、Go言語では名前の先頭文字を大文字にするかどうかでアクセス範囲が変化します。小文字にすることで他パッケージから直接構造体の中身の変数にアクセスさせず、メソッドを経由してのみアクセスを許可することも可能です。

変数の値を変更したい場合

上記の例では、変数の値を参照し、三角形の面積を計算しました。

もし、変数の値を変更したい場合は以下のようにレシーバー部分をポインタにします。

package main

import "fmt"

type Triangle struct {
	Base   int
	Height int
}

func (t Triangle) Area() int {
	return t.Base * t.Height / 2
}

func (t *Triangle) SetBase(b int) {
	t.Base = b
}

func main() {
	t := Triangle{Base: 10, Height: 5}
	t.SetBase(20)
	fmt.Println("area = ", t.Area())
}

func (t *Triangle) SetBase(b int)では、tをポインタにして内部で代入を行なっています。こうすることで値を書き換えることができます。

Go言語の慣習としては、1つの型については、ポインタを使うか使わないかを全てのメソッドで統一するのを推奨しているようです

interfaceの使い方

メソッドが理解できたら、次はinterfaceについてです。interfaceは、同じメソッドを持つ複数の型をまとめるための仕組みです。

言葉ではわかりにくいのでサンプルプログラムを使いながら説明します。

package main

import "fmt"

type Shape interface {
	Area() int
}

type Triangle struct {
	Base   int
	Height int
}

type Rectangle struct {
	Width  int
	Height int
}

func (t Triangle) Area() int {
	return t.Base * t.Height / 2
}

func (r Rectangle) Area() int {
	return r.Width * r.Height
}

func PrintArea(s Shape) {
	fmt.Printf("Area = %d\n", s.Area())
}

func main() {
	t := Triangle{Base: 10, Height: 5}
	r := Rectangle{Width: 10, Height: 5}

	PrintArea(t)
	PrintArea(r)

	s := []Shape{r, t}
	for _, e := range s {
		fmt.Println("Area = ", e.Area())
	}

}

このプログラムでは、interface を使って、異なる図形(TriangleRectangle)の面積を共通の関数PrintAreaで表示しています。

インタフェースの定義は以下の部分です。ここでは、ShapeインタフェースがAreaというメソッドを持つことを定義しています。

type Shape interface {
	Area() int
}

PrintArea関数は、インタフェースShapeを引数として受け取り、Area()を呼び出して面積を表示しています。

func PrintArea(s Shape) {
	fmt.Printf("Area = %d\n", s.Area())
}

main関数では、TriangleRectangleの変数を初期化して、これをPrintAreaの引数として渡しています。それぞれ、Areaメソッドを持つのでエラーにならず表示が行われます。

また、以下の部分では、Shapeの配列を定義してループでArea()を呼び出しています。

	s := []Shape{r, t}
	for _, e := range s {
		fmt.Println("Area = ", e.Area())
	}

ポイントは、Area()をメソッドとして定義されていれば、Shapeインタフェースとして扱うことができることです。

言い換えると型は、そのインタフェースのメソッドをすべて実装すれば暗黙的にインタフェースを満たすことができます。

つまり、個別にinterfaceに登録するような宣言を行う必要がありません。

Go言語では、インタフェースを使ってオブジェクト指向言語でいう多態性を実現しているのです。

interfaceとinterface{}との違い

結論から言えば、interface{}は、Goでは任意の型を格納できる特殊な型です。

package main

import "fmt"

func PrintValue(v interface{}) {
	fmt.Printf("値: %v, 型: %T\n", v, v)
}

func main() {
	PrintValue(42)
	PrintValue("hello")
	PrintValue(3.14)
	PrintValue([]int{1, 2, 3})
}
出力例
値: 42, 型: int
値: hello, 型: string
値: 3.14, 型: float64
値: [1 2 3], 型: []int

結論から言えば、interfaceは特定のメソッドを持つインタフェースをまとめるもの、interface{}は任意の型を収納できる特殊な型です。

どちらもinterfaceなので混乱しますが、この2つは全く別物ですので注意する必要があります。

それぞれの用途は以下のようになります。

用途使用するインタフェース
任意の型を扱いたいinterface{}
特定の振る舞いを抽象化したいinterface(メソッドあり)

Go 1.18以降、anyinterface{}の型エイリアスとして導入され、より直感的に「任意の型」を表現できるようになりました。実質的な違いはなく、どちらを使っても同じ意味ですが、コードの可読性を高めたい場合はanyの使用が推奨される場面もあります。

まとめ

この記事では、Go言語のメソッドとinterfaceについて解説しました。Go言語の独自のものですが概念的には他の言語にも存在するもので、特にオブジェクト指向言語などを使ったことがある場合は理解しやすいと思います。個人的には、慣れれば案外使いやすいと思っています。

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

ABOUT ME
ある/Aru
ある/Aru
IT&機械学習エンジニア/ファイナンシャルプランナー(CFP®)
専門分野は並列処理・画像処理・機械学習・ディープラーニング。プログラミング言語はC, C++, Go, Pythonを中心として色々利用。現在は、Kaggle, 競プロなどをしながら悠々自適に活動中
記事URLをコピーしました